Compare commits

...

91 Commits

Author SHA1 Message Date
07d4085201 util/reflect: fix reflect methods
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-10-27 12:55:48 +03:00
45f30c0be3 util/reflect: ZeroFieldByPath and SetFieldByPath
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-10-26 14:12:37 +03:00
bcaea675a7 util/reflect: add method to zero struct field
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-10-25 14:41:19 +03:00
3087ba1d73 regen
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-10-23 23:45:53 +03:00
3f5b19497c meter: add Clone method
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-10-09 23:50:57 +03:00
37d937d7ae meter: add missing options
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-10-09 19:16:45 +03:00
7d68f2396e tracer: dont return noop from context
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-10-07 22:46:47 +03:00
0854a7ea72 micro: add simple test
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-10-07 20:59:27 +03:00
5eb0e56373 move all imports to own domain
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-10-02 19:55:07 +03:00
ada59119cc util/http: add test case
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-10-02 18:34:22 +03:00
8abc913b28 codec: add MarshalAppend func
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-10-01 01:08:24 +03:00
3247d144a8 lint fixes
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-09-30 21:13:13 +03:00
7b2e3cc8aa lint fixes
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-09-30 21:00:02 +03:00
8688179acd lint fixes
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-09-30 20:32:59 +03:00
3e40bac5f4 config: add helper funcs
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-09-30 01:24:16 +03:00
e3fee6f8a6 util/http: add trie case insesitive matching
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-09-29 13:41:47 +03:00
15c020fac5 fix lint
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-09-29 13:19:07 +03:00
3bc046e5d4 broker/memory: simplify code
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-09-29 13:10:11 +03:00
542f36cfa5 util/reflect: fix tests, lint fixes
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-09-29 13:09:48 +03:00
8237e6a08e util/router: drop google copy of pattern matcher in favour of util/http trie
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-09-29 13:09:22 +03:00
ecb60e4dc5 fix lint
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-09-28 23:43:43 +03:00
a1999ff81c util/http: trie add more tests
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-09-28 01:02:28 +03:00
d0f2bc8346 util/http: add trie matching func
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-09-27 23:30:53 +03:00
dependabot[bot]
dd29bf457e chore(deps): bump actions/github-script from 4 to 5 (#58)
Bumps [actions/github-script](https://github.com/actions/github-script) from 4 to 5.
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](https://github.com/actions/github-script/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/github-script
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-09-27 09:33:25 +03:00
d062c248e3 codec: fieldaligment
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-09-22 17:09:26 +03:00
875f66d36e codec: implement proto v1 message for Frame
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-09-22 16:59:52 +03:00
818a0e6356 codec: add context helper funcs
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-09-22 01:07:27 +03:00
56e02ec463 codec: add ability to pass codec options
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-09-22 00:57:10 +03:00
6ca851401d update workflow
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-09-21 21:46:23 +03:00
bd8216b397 update workflows
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-09-17 07:47:23 +03:00
2b13b3f128 Revert "update workflows"
This reverts commit 9957380b6d.
2021-09-17 07:42:46 +03:00
9957380b6d update workflows
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-09-17 07:41:17 +03:00
e10f8c0fa0 util/id: move tests to micro-tests repo
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-09-16 15:31:01 +03:00
45252fe4a6 enable automerge
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-09-16 10:30:59 +03:00
faad082efe util/rand: add Shuffle func
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-09-15 17:51:25 +03:00
8ab35cbd9b update dependabot
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-09-12 16:17:28 +03:00
ad58ab6943 fix codeql issue
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-09-06 10:51:13 +03:00
0e97049e1d Create SECURITY.md 2021-09-06 10:49:18 +03:00
edb0bbf9cf add codeql
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-09-06 10:30:21 +03:00
dependabot[bot]
1b01bd22a6 build(deps): bump github.com/unistack-org/micro-proto (#57) 2021-09-06 06:56:44 +00:00
2fbaa26f0f logger: add Clone method
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-08-30 16:21:01 +03:00
35d3e4b332 logger: breaking changes to log level parsing
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-08-30 16:21:01 +03:00
dependabot[bot]
e98a93d530 build(deps): bump github.com/unistack-org/micro-proto (#56)
Bumps [github.com/unistack-org/micro-proto](https://github.com/unistack-org/micro-proto) from 0.0.5 to 0.0.8.
- [Release notes](https://github.com/unistack-org/micro-proto/releases)
- [Commits](https://github.com/unistack-org/micro-proto/compare/v0.0.5...v0.0.8)

---
updated-dependencies:
- dependency-name: github.com/unistack-org/micro-proto
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-08-30 16:20:10 +03:00
e3545532e8 minor changes
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-08-21 01:00:10 +03:00
09653c2fb2 util/id: specify default size for uuid behaviour
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-08-20 22:48:03 +03:00
70adfeab0d fix flow
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-08-20 22:44:17 +03:00
a45b672c98 drop uuid and use modified nanoid
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-08-20 22:40:48 +03:00
4509323cae update and regen all
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-08-16 23:56:50 +03:00
b3f4c670d5 regen all
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-08-16 18:57:19 +03:00
778dd449e2 logger: add NewStdLogger and RedirectStdLogger
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-08-06 13:45:11 +03:00
1d16983b67 logger: add NewStdLogger that can be used as std *log.Logger
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-08-06 11:52:04 +03:00
f386bffd37 logger: change logger interface
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-08-06 02:15:57 +03:00
772bde7938 network/tunnel/broker: fix metadata compile issue
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-08-06 02:14:56 +03:00
ea16f5f825 config/default: not implement watcher as it cant change
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-08-04 16:04:58 +03:00
c2f34df493 config: minor changes to split config and watcher files
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-08-04 13:51:43 +03:00
efe215cd60 config/default: watcher send changes only on non nil
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-08-04 12:25:29 +03:00
b4f332bf0d config/default: return error on Next() call
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-08-04 01:15:50 +03:00
f47fbb1030 config: add jitter interval for watcher to avoid dos
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-08-04 00:37:56 +03:00
1e8e57a708 config/default: minor changes
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-08-03 00:49:21 +03:00
dependabot[bot]
5d0959b0a1 build(deps): bump github.com/golang-jwt/jwt (#54)
Bumps [github.com/golang-jwt/jwt](https://github.com/golang-jwt/jwt) from 3.2.1+incompatible to 3.2.2+incompatible.
- [Release notes](https://github.com/golang-jwt/jwt/releases)
- [Changelog](https://github.com/golang-jwt/jwt/blob/main/VERSION_HISTORY.md)
- [Commits](https://github.com/golang-jwt/jwt/compare/v3.2.1...v3.2.2)

---
updated-dependencies:
- dependency-name: github.com/golang-jwt/jwt
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-08-03 00:27:35 +03:00
fa8fb3aed7 fixes and improvements (#55)
* util/router: sync from github
* config: add watcher interface

Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-08-03 00:24:40 +03:00
cfd2d53a79 config: cleanup tests
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-27 23:58:45 +03:00
d306f77ffc util/token/jwt: change library
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-27 23:58:29 +03:00
e5b0a7e20d server: add BatchSubscriber
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-27 23:58:06 +03:00
9a5b158b4d change jwt lib
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-27 12:43:56 +03:00
af8d81f3c6 logger: add DefaultCallerSkipCount
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-26 09:48:15 +03:00
5c9b3dae33 broker: improve option naming, move BatchBroker to Broker interface
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-24 16:16:18 +03:00
9f3957d101 client: improve option naming, add BatchPublish to noop client
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-24 16:14:42 +03:00
8fd8bdcb39 logger: fix default logger funcs
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-24 15:22:01 +03:00
80e3d239ab broker/memory: optimize
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-23 15:12:20 +03:00
419cd486cf broker/memory: cleanup
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-23 15:06:10 +03:00
e64269b2a8 broker: add BatchBroker interface to avoid breaking older brokers
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-23 12:55:36 +03:00
d18429e024 metadata: add HeaderAuthorization
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-23 12:17:00 +03:00
675e121049 metadata: add default headers
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-23 12:03:18 +03:00
d357fb1e0d WIP: broker batch processing
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-22 22:53:44 +03:00
e4673bcc50 remove old cruft
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-22 15:45:44 +03:00
a839f75a2f util/reflect: add new funcs
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-22 15:45:31 +03:00
a7e6d61b95 meter: fast path for only one label
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-21 14:29:13 +03:00
650d167313 meter: add BuildLabels func that sorts and deletes duplicates
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-21 14:10:20 +03:00
c6ba2a91e6 meter: BuildName func to combine metric name with labels into string
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-21 12:39:59 +03:00
7ece08896f server: use 127.0.0.1:0 if no address provided
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-17 01:57:39 +06:00
dependabot[bot]
57f6f23294 build(deps): bump github.com/google/uuid from 1.2.0 to 1.3.0 (#53)
Bumps [github.com/google/uuid](https://github.com/google/uuid) from 1.2.0 to 1.3.0.
- [Release notes](https://github.com/google/uuid/releases)
- [Commits](https://github.com/google/uuid/compare/v1.2.0...v1.3.0)

---
updated-dependencies:
- dependency-name: github.com/google/uuid
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-16 00:27:56 +03:00
09e6fa2fed flow: implement new methods, add Async ExecutionOption
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-16 00:17:16 +03:00
10a09a5c6f flow: improve store
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-15 22:56:34 +03:00
b4e5d9462a util/router: move some messages to Trace level
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-15 22:56:34 +03:00
96aa0b6906 store/memory: fix List
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-15 22:53:12 +03:00
f54658830d store/memory: fixup
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-15 12:11:55 +03:00
1e43122660 store/memory: small fixups for flow usage
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-15 11:59:35 +03:00
42800fa247 flow: improve steps handling
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-14 17:12:54 +03:00
5b9c810653 logger: add compile time test for interface compat
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-14 17:12:09 +03:00
c3def24bf4 store: add Wrappers support, create Namespace wrapper
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-14 17:11:37 +03:00
202 changed files with 4003 additions and 2790 deletions

View File

@@ -11,9 +11,16 @@ updates:
directory: "/" directory: "/"
schedule: schedule:
interval: "daily" interval: "daily"
commit-message:
prefix: "chore"
include: "scope"
# Maintain dependencies for Golang # Maintain dependencies for Golang
- package-ecosystem: "gomod" - package-ecosystem: "gomod"
directory: "/" directory: "/"
schedule: schedule:
interval: "daily" interval: "daily"
commit-message:
prefix: "chore"
include: "scope"

75
.github/workflows/codeql-analysis.yml vendored Normal file
View File

@@ -0,0 +1,75 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
workflow_run:
workflows: ["prbuild"]
types:
- completed
push:
branches: [ master ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ master ]
schedule:
- cron: '34 1 * * 0'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'go' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
# Learn more:
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
steps:
- name: Checkout repository
uses: actions/checkout@v2
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

View File

@@ -0,0 +1,66 @@
name: "prautomerge"
on:
workflow_run:
workflows: ["prbuild"]
types:
- completed
permissions:
contents: write
pull-requests: write
jobs:
Dependabot-Automerge:
runs-on: ubuntu-latest
# Contains workaround to execute if dependabot updates the PR by checking for the base branch in the linked PR
# The the github.event.workflow_run.event value is 'push' and not 'pull_request'
# dont work with multiple workflows when last returns success
if: >-
github.event.workflow_run.conclusion == 'success'
&& github.actor == 'dependabot[bot]'
&& github.event.sender.login == 'dependabot[bot]'
&& github.event.sender.type == 'Bot'
&& (github.event.workflow_run.event == 'pull_request'
|| (github.event.workflow_run.event == 'push' && github.event.workflow_run.pull_requests[0].base.ref == github.event.repository.default_branch ))
steps:
- name: Approve Changes and Merge changes if label 'dependencies' is set
uses: actions/github-script@v5
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
console.log(context.payload.workflow_run);
var labelNames = await github.paginate(
github.issues.listLabelsOnIssue,
{
repo: context.repo.repo,
owner: context.repo.owner,
issue_number: context.payload.workflow_run.pull_requests[0].number,
},
(response) => response.data.map(
(label) => label.name
)
);
console.log(labelNames);
if (labelNames.includes('dependencies')) {
console.log('Found label');
await github.pulls.createReview({
repo: context.repo.repo,
owner: context.repo.owner,
pull_number: context.payload.workflow_run.pull_requests[0].number,
event: 'APPROVE'
});
console.log('Approved PR');
await github.pulls.merge({
repo: context.repo.repo,
owner: context.repo.owner,
pull_number: context.payload.workflow_run.pull_requests[0].number,
});
console.log('Merged PR');
}

View File

@@ -30,7 +30,7 @@ linters:
- gofmt - gofmt
- gofumpt - gofumpt
- goimports - goimports
- golint - revive
- gosec - gosec
- makezero - makezero
- misspell - misspell

15
SECURITY.md Normal file
View File

@@ -0,0 +1,15 @@
# Security Policy
## Supported Versions
Use this section to tell people about which versions of your project are
currently being supported with security updates.
| Version | Supported |
| ------- | ------------------ |
| 3.7.x | :white_check_mark: |
| < 3.7.0 | :x: |
## Reporting a Vulnerability
If you find any issue, please create github issue in this repo

View File

@@ -1,15 +1,16 @@
package api package api // import "go.unistack.org/micro/v3/api"
import ( import (
"errors" "errors"
"regexp" "regexp"
"strings" "strings"
"github.com/unistack-org/micro/v3/metadata" "go.unistack.org/micro/v3/metadata"
"github.com/unistack-org/micro/v3/register" "go.unistack.org/micro/v3/register"
"github.com/unistack-org/micro/v3/server" "go.unistack.org/micro/v3/server"
) )
// nolint: revive
// Api interface // Api interface
type Api interface { type Api interface {
// Initialise options // Initialise options
@@ -125,14 +126,14 @@ func Validate(e *Endpoint) error {
ps := p[0] ps := p[0]
pe := p[len(p)-1] pe := p[len(p)-1]
if ps == '^' && pe == '$' { switch {
_, err := regexp.CompilePOSIX(p) case ps == '^' && pe == '$':
if err != nil { if _, err := regexp.CompilePOSIX(p); err != nil {
return err return err
} }
} else if ps == '^' && pe != '$' { case ps == '^' && pe != '$':
return errors.New("invalid path") return errors.New("invalid path")
} else if ps != '^' && pe == '$' { case ps != '^' && pe == '$':
return errors.New("invalid path") return errors.New("invalid path")
} }
} }

View File

@@ -1,5 +1,5 @@
// Package handler provides http handlers // Package handler provides http handlers
package handler package handler // import "go.unistack.org/micro/v3/api/handler"
import ( import (
"net/http" "net/http"

View File

@@ -1,9 +1,9 @@
package handler package handler
import ( import (
"github.com/unistack-org/micro/v3/api/router" "go.unistack.org/micro/v3/api/router"
"github.com/unistack-org/micro/v3/client" "go.unistack.org/micro/v3/client"
"github.com/unistack-org/micro/v3/logger" "go.unistack.org/micro/v3/logger"
) )
// DefaultMaxRecvSize specifies max recv size for handler // DefaultMaxRecvSize specifies max recv size for handler

View File

@@ -1,12 +1,12 @@
// Package grpc resolves a grpc service like /greeter.Say/Hello to greeter service // Package grpc resolves a grpc service like /greeter.Say/Hello to greeter service
package grpc package grpc // import "go.unistack.org/micro/v3/api/resolver/grpc"
import ( import (
"errors" "errors"
"net/http" "net/http"
"strings" "strings"
"github.com/unistack-org/micro/v3/api/resolver" "go.unistack.org/micro/v3/api/resolver"
) )
// Resolver struct // Resolver struct

View File

@@ -1,10 +1,10 @@
// Package host resolves using http host // Package host resolves using http host
package host package host // import "go.unistack.org/micro/v3/api/resolver/host"
import ( import (
"net/http" "net/http"
"github.com/unistack-org/micro/v3/api/resolver" "go.unistack.org/micro/v3/api/resolver"
) )
type hostResolver struct { type hostResolver struct {

View File

@@ -3,7 +3,7 @@ package resolver
import ( import (
"context" "context"
"github.com/unistack-org/micro/v3/register" "go.unistack.org/micro/v3/register"
) )
// Options struct // Options struct

View File

@@ -1,11 +1,11 @@
// Package path resolves using http path // Package path resolves using http path
package path package path // import "go.unistack.org/micro/v3/api/resolver/path"
import ( import (
"net/http" "net/http"
"strings" "strings"
"github.com/unistack-org/micro/v3/api/resolver" "go.unistack.org/micro/v3/api/resolver"
) )
// Resolver the path resolver // Resolver the path resolver

View File

@@ -1,5 +1,5 @@
// Package resolver resolves a http request to an endpoint // Package resolver resolves a http request to an endpoint
package resolver package resolver // import "go.unistack.org/micro/v3/api/resolver"
import ( import (
"errors" "errors"

View File

@@ -1,14 +1,14 @@
// Package subdomain is a resolver which uses the subdomain to determine the domain to route to. It // Package subdomain is a resolver which uses the subdomain to determine the domain to route to. It
// offloads the endpoint resolution to a child resolver which is provided in New. // offloads the endpoint resolution to a child resolver which is provided in New.
package subdomain package subdomain // import "go.unistack.org/micro/v3/api/resolver/subdomain"
import ( import (
"net" "net"
"net/http" "net/http"
"strings" "strings"
"github.com/unistack-org/micro/v3/api/resolver" "go.unistack.org/micro/v3/api/resolver"
"github.com/unistack-org/micro/v3/logger" "go.unistack.org/micro/v3/logger"
"golang.org/x/net/publicsuffix" "golang.org/x/net/publicsuffix"
) )

View File

@@ -5,7 +5,7 @@ import (
"net/url" "net/url"
"testing" "testing"
"github.com/unistack-org/micro/v3/api/resolver/vpath" "go.unistack.org/micro/v3/api/resolver/vpath"
) )
func TestResolve(t *testing.T) { func TestResolve(t *testing.T) {

View File

@@ -1,5 +1,5 @@
// Package vpath resolves using http path and recognised versioned urls // Package vpath resolves using http path and recognised versioned urls
package vpath package vpath // import "go.unistack.org/micro/v3/api/resolver/vpath"
import ( import (
"errors" "errors"
@@ -7,7 +7,7 @@ import (
"regexp" "regexp"
"strings" "strings"
"github.com/unistack-org/micro/v3/api/resolver" "go.unistack.org/micro/v3/api/resolver"
) )
// NewResolver creates new vpath api resolver // NewResolver creates new vpath api resolver

View File

@@ -3,10 +3,10 @@ package router
import ( import (
"context" "context"
"github.com/unistack-org/micro/v3/api/resolver" "go.unistack.org/micro/v3/api/resolver"
"github.com/unistack-org/micro/v3/api/resolver/vpath" "go.unistack.org/micro/v3/api/resolver/vpath"
"github.com/unistack-org/micro/v3/logger" "go.unistack.org/micro/v3/logger"
"github.com/unistack-org/micro/v3/register" "go.unistack.org/micro/v3/register"
) )
// Options holds the options for api router // Options holds the options for api router

View File

@@ -1,10 +1,10 @@
// Package router provides api service routing // Package router provides api service routing
package router package router // import "go.unistack.org/micro/v3/api/router"
import ( import (
"net/http" "net/http"
"github.com/unistack-org/micro/v3/api" "go.unistack.org/micro/v3/api"
) )
// DefaultRouter contains default router implementation // DefaultRouter contains default router implementation

View File

@@ -1,12 +1,12 @@
// Package auth provides authentication and authorization capability // Package auth provides authentication and authorization capability
package auth package auth // import "go.unistack.org/micro/v3/auth"
import ( import (
"context" "context"
"errors" "errors"
"time" "time"
"github.com/unistack-org/micro/v3/metadata" "go.unistack.org/micro/v3/metadata"
) )
const ( const (
@@ -55,7 +55,7 @@ type Auth interface {
type Account struct { type Account struct {
// Metadata any other associated metadata // Metadata any other associated metadata
Metadata metadata.Metadata `json:"metadata"` Metadata metadata.Metadata `json:"metadata"`
// ID of the account e.g. email or uuid // ID of the account e.g. email or id
ID string `json:"id"` ID string `json:"id"`
// Type of the account, e.g. service // Type of the account, e.g. service
Type string `json:"type"` Type string `json:"type"`

View File

@@ -1,7 +1,7 @@
package auth package auth
import ( import (
"github.com/google/uuid" "go.unistack.org/micro/v3/util/id"
) )
type noopAuth struct { type noopAuth struct {
@@ -61,11 +61,11 @@ func (n *noopAuth) Verify(acc *Account, res *Resource, opts ...VerifyOption) err
// Inspect a token // Inspect a token
func (n *noopAuth) Inspect(token string) (*Account, error) { func (n *noopAuth) Inspect(token string) (*Account, error) {
uid, err := uuid.NewRandom() id, err := id.New()
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &Account{ID: uid.String(), Issuer: n.Options().Issuer}, nil return &Account{ID: id, Issuer: n.Options().Issuer}, nil
} }
// Token generation using an account id and secret // Token generation using an account id and secret

View File

@@ -4,11 +4,11 @@ import (
"context" "context"
"time" "time"
"github.com/unistack-org/micro/v3/logger" "go.unistack.org/micro/v3/logger"
"github.com/unistack-org/micro/v3/metadata" "go.unistack.org/micro/v3/metadata"
"github.com/unistack-org/micro/v3/meter" "go.unistack.org/micro/v3/meter"
"github.com/unistack-org/micro/v3/store" "go.unistack.org/micro/v3/store"
"github.com/unistack-org/micro/v3/tracer" "go.unistack.org/micro/v3/tracer"
) )
// NewOptions creates Options struct from slice of options // NewOptions creates Options struct from slice of options

View File

@@ -1,38 +1,86 @@
// Package broker is an interface used for asynchronous messaging // Package broker is an interface used for asynchronous messaging
package broker package broker // import "go.unistack.org/micro/v3/broker"
import ( import (
"context" "context"
"errors" "errors"
"github.com/unistack-org/micro/v3/metadata" "go.unistack.org/micro/v3/metadata"
) )
// DefaultBroker default broker // DefaultBroker default memory broker
var DefaultBroker Broker = NewBroker() var DefaultBroker Broker = NewBroker()
var (
// ErrNotConnected returns when broker used but not connected yet
ErrNotConnected = errors.New("broker not connected")
// ErrDisconnected returns when broker disconnected
ErrDisconnected = errors.New("broker disconnected")
)
// Broker is an interface used for asynchronous messaging. // Broker is an interface used for asynchronous messaging.
type Broker interface { type Broker interface {
// Name returns broker instance name
Name() string Name() string
Init(...Option) error // Init initilize broker
Init(opts ...Option) error
// Options returns broker options
Options() Options Options() Options
// Address return configured address
Address() string Address() string
Connect(context.Context) error // Connect connects to broker
Disconnect(context.Context) error Connect(ctx context.Context) error
Publish(context.Context, string, *Message, ...PublishOption) error // Disconnect disconnect from broker
Subscribe(context.Context, string, Handler, ...SubscribeOption) (Subscriber, error) Disconnect(ctx context.Context) error
// Publish message to broker topic
Publish(ctx context.Context, topic string, msg *Message, opts ...PublishOption) error
// Subscribe subscribes to topic message via handler
Subscribe(ctx context.Context, topic string, h Handler, opts ...SubscribeOption) (Subscriber, error)
// BatchPublish messages to broker with multiple topics
BatchPublish(ctx context.Context, msgs []*Message, opts ...PublishOption) error
// BatchSubscribe subscribes to topic messages via handler
BatchSubscribe(ctx context.Context, topic string, h BatchHandler, opts ...SubscribeOption) (Subscriber, error)
// String type of broker
String() string String() string
} }
// Handler is used to process messages via a subscription of a topic. // Handler is used to process messages via a subscription of a topic.
type Handler func(Event) error type Handler func(Event) error
// Events contains multiple events
type Events []Event
func (evs Events) Ack() error {
var err error
for _, ev := range evs {
if err = ev.Ack(); err != nil {
return err
}
}
return nil
}
func (evs Events) SetError(err error) {
for _, ev := range evs {
ev.SetError(err)
}
}
// BatchHandler is used to process messages in batches via a subscription of a topic.
type BatchHandler func(Events) error
// Event is given to a subscription handler for processing // Event is given to a subscription handler for processing
type Event interface { type Event interface {
// Topic returns event topic
Topic() string Topic() string
// Message returns broker message
Message() *Message Message() *Message
// Ack acknowledge message
Ack() error Ack() error
// Error returns message error (like decoding errors or some other)
Error() error Error() error
// SetError set event processing error
SetError(err error)
} }
// RawMessage is a raw encoded JSON value. // RawMessage is a raw encoded JSON value.
@@ -58,13 +106,25 @@ func (m *RawMessage) UnmarshalJSON(data []byte) error {
// Message is used to transfer data // Message is used to transfer data
type Message struct { type Message struct {
Header metadata.Metadata // contains message metadata // Header contains message metadata
Body RawMessage // contains message body Header metadata.Metadata
// Body contains message body
Body RawMessage
}
// NewMessage create broker message with topic filled
func NewMessage(topic string) *Message {
m := &Message{Header: metadata.New(2)}
m.Header.Set(metadata.HeaderTopic, topic)
return m
} }
// Subscriber is a convenience return type for the Subscribe method // Subscriber is a convenience return type for the Subscribe method
type Subscriber interface { type Subscriber interface {
// Options returns subscriber options
Options() SubscribeOptions Options() SubscribeOptions
// Topic returns topic for subscription
Topic() string Topic() string
Unsubscribe(context.Context) error // Unsubscribe from topic
Unsubscribe(ctx context.Context) error
} }

View File

@@ -2,18 +2,18 @@ package broker
import ( import (
"context" "context"
"errors"
"sync" "sync"
"github.com/google/uuid" "go.unistack.org/micro/v3/logger"
"github.com/unistack-org/micro/v3/logger" "go.unistack.org/micro/v3/metadata"
maddr "github.com/unistack-org/micro/v3/util/addr" maddr "go.unistack.org/micro/v3/util/addr"
mnet "github.com/unistack-org/micro/v3/util/net" "go.unistack.org/micro/v3/util/id"
"github.com/unistack-org/micro/v3/util/rand" mnet "go.unistack.org/micro/v3/util/net"
"go.unistack.org/micro/v3/util/rand"
) )
type memoryBroker struct { type memoryBroker struct {
Subscribers map[string][]*memorySubscriber subscribers map[string][]*memorySubscriber
addr string addr string
opts Options opts Options
sync.RWMutex sync.RWMutex
@@ -28,12 +28,13 @@ type memoryEvent struct {
} }
type memorySubscriber struct { type memorySubscriber struct {
ctx context.Context ctx context.Context
exit chan bool exit chan bool
handler Handler handler Handler
id string batchhandler BatchHandler
topic string id string
opts SubscribeOptions topic string
opts SubscribeOptions
} }
func (m *memoryBroker) Options() Options { func (m *memoryBroker) Options() Options {
@@ -77,7 +78,6 @@ func (m *memoryBroker) Disconnect(ctx context.Context) error {
} }
m.connected = false m.connected = false
return nil return nil
} }
@@ -89,73 +89,168 @@ func (m *memoryBroker) Init(opts ...Option) error {
} }
func (m *memoryBroker) Publish(ctx context.Context, topic string, msg *Message, opts ...PublishOption) error { func (m *memoryBroker) Publish(ctx context.Context, topic string, msg *Message, opts ...PublishOption) error {
msg.Header.Set(metadata.HeaderTopic, topic)
return m.publish(ctx, []*Message{msg}, opts...)
}
func (m *memoryBroker) BatchPublish(ctx context.Context, msgs []*Message, opts ...PublishOption) error {
return m.publish(ctx, msgs, opts...)
}
func (m *memoryBroker) publish(ctx context.Context, msgs []*Message, opts ...PublishOption) error {
m.RLock() m.RLock()
if !m.connected { if !m.connected {
m.RUnlock() m.RUnlock()
return errors.New("not connected") return ErrNotConnected
} }
subs, ok := m.Subscribers[topic]
m.RUnlock() m.RUnlock()
if !ok {
return nil
}
var v interface{} var err error
if m.opts.Codec != nil {
buf, err := m.opts.Codec.Marshal(msg) select {
if err != nil { case <-ctx.Done():
return err return ctx.Err()
default:
options := NewPublishOptions(opts...)
msgTopicMap := make(map[string]Events)
for _, v := range msgs {
p := &memoryEvent{opts: m.opts}
if m.opts.Codec == nil || options.BodyOnly {
p.topic, _ = v.Header.Get(metadata.HeaderTopic)
p.message = v.Body
} else {
p.topic, _ = v.Header.Get(metadata.HeaderTopic)
p.message, err = m.opts.Codec.Marshal(v)
if err != nil {
return err
}
}
msgTopicMap[p.topic] = append(msgTopicMap[p.topic], p)
} }
v = buf
} else {
v = msg
}
p := &memoryEvent{ beh := m.opts.BatchErrorHandler
topic: topic, eh := m.opts.ErrorHandler
message: v,
opts: m.opts,
}
eh := m.opts.ErrorHandler for t, ms := range msgTopicMap {
m.RLock()
for _, sub := range subs { subs, ok := m.subscribers[t]
if err := sub.handler(p); err != nil { m.RUnlock()
p.err = err if !ok {
if sub.opts.ErrorHandler != nil { continue
eh = sub.opts.ErrorHandler
} }
if eh != nil {
eh(p) for _, sub := range subs {
} else if m.opts.Logger.V(logger.ErrorLevel) { if sub.opts.BatchErrorHandler != nil {
m.opts.Logger.Error(m.opts.Context, err.Error()) beh = sub.opts.BatchErrorHandler
}
if sub.opts.ErrorHandler != nil {
eh = sub.opts.ErrorHandler
}
switch {
// batch processing
case sub.batchhandler != nil:
if err = sub.batchhandler(ms); err != nil {
ms.SetError(err)
if beh != nil {
_ = beh(ms)
} else if m.opts.Logger.V(logger.ErrorLevel) {
m.opts.Logger.Error(m.opts.Context, err.Error())
}
} else if sub.opts.AutoAck {
if err = ms.Ack(); err != nil {
m.opts.Logger.Errorf(m.opts.Context, "ack failed: %v", err)
}
}
// single processing
case sub.handler != nil:
for _, p := range ms {
if err = sub.handler(p); err != nil {
p.SetError(err)
if eh != nil {
_ = eh(p)
} else if m.opts.Logger.V(logger.ErrorLevel) {
m.opts.Logger.Error(m.opts.Context, err.Error())
}
} else if sub.opts.AutoAck {
if err = p.Ack(); err != nil {
m.opts.Logger.Errorf(m.opts.Context, "ack failed: %v", err)
}
}
}
}
} }
continue
} }
} }
return nil return nil
} }
func (m *memoryBroker) Subscribe(ctx context.Context, topic string, handler Handler, opts ...SubscribeOption) (Subscriber, error) { func (m *memoryBroker) BatchSubscribe(ctx context.Context, topic string, handler BatchHandler, opts ...SubscribeOption) (Subscriber, error) {
m.RLock() m.RLock()
if !m.connected { if !m.connected {
m.RUnlock() m.RUnlock()
return nil, errors.New("not connected") return nil, ErrNotConnected
} }
m.RUnlock() m.RUnlock()
options := NewSubscribeOptions(opts...) sid, err := id.New()
id, err := uuid.NewRandom()
if err != nil { if err != nil {
return nil, err return nil, err
} }
options := NewSubscribeOptions(opts...)
sub := &memorySubscriber{
exit: make(chan bool, 1),
id: sid,
topic: topic,
batchhandler: handler,
opts: options,
ctx: ctx,
}
m.Lock()
m.subscribers[topic] = append(m.subscribers[topic], sub)
m.Unlock()
go func() {
<-sub.exit
m.Lock()
newSubscribers := make([]*memorySubscriber, 0, len(m.subscribers)-1)
for _, sb := range m.subscribers[topic] {
if sb.id == sub.id {
continue
}
newSubscribers = append(newSubscribers, sb)
}
m.subscribers[topic] = newSubscribers
m.Unlock()
}()
return sub, nil
}
func (m *memoryBroker) Subscribe(ctx context.Context, topic string, handler Handler, opts ...SubscribeOption) (Subscriber, error) {
m.RLock()
if !m.connected {
m.RUnlock()
return nil, ErrNotConnected
}
m.RUnlock()
sid, err := id.New()
if err != nil {
return nil, err
}
options := NewSubscribeOptions(opts...)
sub := &memorySubscriber{ sub := &memorySubscriber{
exit: make(chan bool, 1), exit: make(chan bool, 1),
id: id.String(), id: sid,
topic: topic, topic: topic,
handler: handler, handler: handler,
opts: options, opts: options,
@@ -163,20 +258,20 @@ func (m *memoryBroker) Subscribe(ctx context.Context, topic string, handler Hand
} }
m.Lock() m.Lock()
m.Subscribers[topic] = append(m.Subscribers[topic], sub) m.subscribers[topic] = append(m.subscribers[topic], sub)
m.Unlock() m.Unlock()
go func() { go func() {
<-sub.exit <-sub.exit
m.Lock() m.Lock()
var newSubscribers []*memorySubscriber newSubscribers := make([]*memorySubscriber, 0, len(m.subscribers)-1)
for _, sb := range m.Subscribers[topic] { for _, sb := range m.subscribers[topic] {
if sb.id == sub.id { if sb.id == sub.id {
continue continue
} }
newSubscribers = append(newSubscribers, sb) newSubscribers = append(newSubscribers, sb)
} }
m.Subscribers[topic] = newSubscribers m.subscribers[topic] = newSubscribers
m.Unlock() m.Unlock()
}() }()
@@ -221,6 +316,10 @@ func (m *memoryEvent) Error() error {
return m.err return m.err
} }
func (m *memoryEvent) SetError(err error) {
m.err = err
}
func (m *memorySubscriber) Options() SubscribeOptions { func (m *memorySubscriber) Options() SubscribeOptions {
return m.opts return m.opts
} }
@@ -238,6 +337,6 @@ func (m *memorySubscriber) Unsubscribe(ctx context.Context) error {
func NewBroker(opts ...Option) Broker { func NewBroker(opts ...Option) Broker {
return &memoryBroker{ return &memoryBroker{
opts: NewOptions(opts...), opts: NewOptions(opts...),
Subscribers: make(map[string][]*memorySubscriber), subscribers: make(map[string][]*memorySubscriber),
} }
} }

View File

@@ -4,8 +4,56 @@ import (
"context" "context"
"fmt" "fmt"
"testing" "testing"
"go.unistack.org/micro/v3/metadata"
) )
func TestMemoryBatchBroker(t *testing.T) {
b := NewBroker()
ctx := context.Background()
if err := b.Connect(ctx); err != nil {
t.Fatalf("Unexpected connect error %v", err)
}
topic := "test"
count := 10
fn := func(evts Events) error {
return evts.Ack()
}
sub, err := b.BatchSubscribe(ctx, topic, fn)
if err != nil {
t.Fatalf("Unexpected error subscribing %v", err)
}
msgs := make([]*Message, 0, count)
for i := 0; i < count; i++ {
message := &Message{
Header: map[string]string{
metadata.HeaderTopic: topic,
"foo": "bar",
"id": fmt.Sprintf("%d", i),
},
Body: []byte(`"hello world"`),
}
msgs = append(msgs, message)
}
if err := b.BatchPublish(ctx, msgs); err != nil {
t.Fatalf("Unexpected error publishing %v", err)
}
if err := sub.Unsubscribe(ctx); err != nil {
t.Fatalf("Unexpected error unsubscribing from %s: %v", topic, err)
}
if err := b.Disconnect(ctx); err != nil {
t.Fatalf("Unexpected connect error %v", err)
}
}
func TestMemoryBroker(t *testing.T) { func TestMemoryBroker(t *testing.T) {
b := NewBroker() b := NewBroker()
ctx := context.Background() ctx := context.Background()
@@ -26,20 +74,27 @@ func TestMemoryBroker(t *testing.T) {
t.Fatalf("Unexpected error subscribing %v", err) t.Fatalf("Unexpected error subscribing %v", err)
} }
msgs := make([]*Message, 0, count)
for i := 0; i < count; i++ { for i := 0; i < count; i++ {
message := &Message{ message := &Message{
Header: map[string]string{ Header: map[string]string{
"foo": "bar", metadata.HeaderTopic: topic,
"id": fmt.Sprintf("%d", i), "foo": "bar",
"id": fmt.Sprintf("%d", i),
}, },
Body: []byte(`"hello world"`), Body: []byte(`"hello world"`),
} }
msgs = append(msgs, message)
if err := b.Publish(ctx, topic, message); err != nil { if err := b.Publish(ctx, topic, message); err != nil {
t.Fatalf("Unexpected error publishing %d", i) t.Fatalf("Unexpected error publishing %d err: %v", i, err)
} }
} }
if err := b.BatchPublish(ctx, msgs); err != nil {
t.Fatalf("Unexpected error publishing %v", err)
}
if err := sub.Unsubscribe(ctx); err != nil { if err := sub.Unsubscribe(ctx); err != nil {
t.Fatalf("Unexpected error unsubscribing from %s: %v", topic, err) t.Fatalf("Unexpected error unsubscribing from %s: %v", topic, err)
} }

View File

@@ -3,12 +3,13 @@ package broker
import ( import (
"context" "context"
"crypto/tls" "crypto/tls"
"time"
"github.com/unistack-org/micro/v3/codec" "go.unistack.org/micro/v3/codec"
"github.com/unistack-org/micro/v3/logger" "go.unistack.org/micro/v3/logger"
"github.com/unistack-org/micro/v3/meter" "go.unistack.org/micro/v3/meter"
"github.com/unistack-org/micro/v3/register" "go.unistack.org/micro/v3/register"
"github.com/unistack-org/micro/v3/tracer" "go.unistack.org/micro/v3/tracer"
) )
// Options struct // Options struct
@@ -29,6 +30,8 @@ type Options struct {
TLSConfig *tls.Config TLSConfig *tls.Config
// ErrorHandler used when broker can't unmarshal incoming message // ErrorHandler used when broker can't unmarshal incoming message
ErrorHandler Handler ErrorHandler Handler
// BatchErrorHandler used when broker can't unmashal incoming messages
BatchErrorHandler BatchHandler
// Name holds the broker name // Name holds the broker name
Name string Name string
// Addrs holds the broker address // Addrs holds the broker address
@@ -71,11 +74,9 @@ func NewPublishOptions(opts ...PublishOption) PublishOptions {
options := PublishOptions{ options := PublishOptions{
Context: context.Background(), Context: context.Background(),
} }
for _, o := range opts { for _, o := range opts {
o(&options) o(&options)
} }
return options return options
} }
@@ -85,12 +86,18 @@ type SubscribeOptions struct {
Context context.Context Context context.Context
// ErrorHandler used when broker can't unmarshal incoming message // ErrorHandler used when broker can't unmarshal incoming message
ErrorHandler Handler ErrorHandler Handler
// BatchErrorHandler used when broker can't unmashal incoming messages
BatchErrorHandler BatchHandler
// Group holds consumer group // Group holds consumer group
Group string Group string
// AutoAck flag specifies auto ack of incoming message when no error happens // AutoAck flag specifies auto ack of incoming message when no error happens
AutoAck bool AutoAck bool
// BodyOnly flag specifies that message contains only body bytes without header // BodyOnly flag specifies that message contains only body bytes without header
BodyOnly bool BodyOnly bool
// BatchSize flag specifies max batch size
BatchSize int
// BatchWait flag specifies max wait time for batch filling
BatchWait time.Duration
} }
// Option func // Option func
@@ -113,23 +120,6 @@ func PublishContext(ctx context.Context) PublishOption {
} }
} }
// SubscribeOption func
type SubscribeOption func(*SubscribeOptions)
// NewSubscribeOptions creates new SubscribeOptions
func NewSubscribeOptions(opts ...SubscribeOption) SubscribeOptions {
options := SubscribeOptions{
AutoAck: true,
Context: context.Background(),
}
for _, o := range opts {
o(&options)
}
return options
}
// Addrs sets the host addresses to be used by the broker // Addrs sets the host addresses to be used by the broker
func Addrs(addrs ...string) Option { func Addrs(addrs ...string) Option {
return func(o *Options) { return func(o *Options) {
@@ -145,28 +135,6 @@ func Codec(c codec.Codec) Option {
} }
} }
// DisableAutoAck disables auto ack
func DisableAutoAck() SubscribeOption {
return func(o *SubscribeOptions) {
o.AutoAck = false
}
}
// SubscribeAutoAck will disable auto acking of messages
// after they have been handled.
func SubscribeAutoAck(b bool) SubscribeOption {
return func(o *SubscribeOptions) {
o.AutoAck = b
}
}
// SubscribeBodyOnly consumes only body of the message
func SubscribeBodyOnly(b bool) SubscribeOption {
return func(o *SubscribeOptions) {
o.BodyOnly = b
}
}
// ErrorHandler will catch all broker errors that cant be handled // ErrorHandler will catch all broker errors that cant be handled
// in normal way, for example Codec errors // in normal way, for example Codec errors
func ErrorHandler(h Handler) Option { func ErrorHandler(h Handler) Option {
@@ -175,6 +143,14 @@ func ErrorHandler(h Handler) Option {
} }
} }
// BatchErrorHandler will catch all broker errors that cant be handled
// in normal way, for example Codec errors
func BatchErrorHandler(h BatchHandler) Option {
return func(o *Options) {
o.BatchErrorHandler = h
}
}
// SubscribeErrorHandler will catch all broker errors that cant be handled // SubscribeErrorHandler will catch all broker errors that cant be handled
// in normal way, for example Codec errors // in normal way, for example Codec errors
func SubscribeErrorHandler(h Handler) SubscribeOption { func SubscribeErrorHandler(h Handler) SubscribeOption {
@@ -183,6 +159,14 @@ func SubscribeErrorHandler(h Handler) SubscribeOption {
} }
} }
// SubscribeBatchErrorHandler will catch all broker errors that cant be handled
// in normal way, for example Codec errors
func SubscribeBatchErrorHandler(h BatchHandler) SubscribeOption {
return func(o *SubscribeOptions) {
o.BatchErrorHandler = h
}
}
// Queue sets the subscribers queue // Queue sets the subscribers queue
// Deprecated // Deprecated
func Queue(name string) SubscribeOption { func Queue(name string) SubscribeOption {
@@ -246,3 +230,55 @@ func SubscribeContext(ctx context.Context) SubscribeOption {
o.Context = ctx o.Context = ctx
} }
} }
// DisableAutoAck disables auto ack
// Deprecated
func DisableAutoAck() SubscribeOption {
return func(o *SubscribeOptions) {
o.AutoAck = false
}
}
// SubscribeAutoAck contol auto acking of messages
// after they have been handled.
func SubscribeAutoAck(b bool) SubscribeOption {
return func(o *SubscribeOptions) {
o.AutoAck = b
}
}
// SubscribeBodyOnly consumes only body of the message
func SubscribeBodyOnly(b bool) SubscribeOption {
return func(o *SubscribeOptions) {
o.BodyOnly = b
}
}
// SubscribeBatchSize specifies max batch size
func SubscribeBatchSize(n int) SubscribeOption {
return func(o *SubscribeOptions) {
o.BatchSize = n
}
}
// SubscribeBatchWait specifies max batch wait time
func SubscribeBatchWait(td time.Duration) SubscribeOption {
return func(o *SubscribeOptions) {
o.BatchWait = td
}
}
// SubscribeOption func
type SubscribeOption func(*SubscribeOptions)
// NewSubscribeOptions creates new SubscribeOptions
func NewSubscribeOptions(opts ...SubscribeOption) SubscribeOptions {
options := SubscribeOptions{
AutoAck: true,
Context: context.Background(),
}
for _, o := range opts {
o(&options)
}
return options
}

View File

@@ -1,5 +1,5 @@
// Package build is for building source into a package // Package build is for building source into a package
package build package build // import "go.unistack.org/micro/v3/build"
// Build is an interface for building packages // Build is an interface for building packages
type Build interface { type Build interface {

View File

@@ -4,7 +4,7 @@ import (
"context" "context"
"time" "time"
"github.com/unistack-org/micro/v3/util/backoff" "go.unistack.org/micro/v3/util/backoff"
) )
// BackoffFunc is the backoff call func // BackoffFunc is the backoff call func

View File

@@ -1,12 +1,12 @@
// Package client is an interface for an RPC client // Package client is an interface for an RPC client
package client package client // import "go.unistack.org/micro/v3/client"
import ( import (
"context" "context"
"time" "time"
"github.com/unistack-org/micro/v3/codec" "go.unistack.org/micro/v3/codec"
"github.com/unistack-org/micro/v3/metadata" "go.unistack.org/micro/v3/metadata"
) )
var ( var (
@@ -40,6 +40,7 @@ type Client interface {
Call(ctx context.Context, req Request, rsp interface{}, opts ...CallOption) error Call(ctx context.Context, req Request, rsp interface{}, opts ...CallOption) error
Stream(ctx context.Context, req Request, opts ...CallOption) (Stream, error) Stream(ctx context.Context, req Request, opts ...CallOption) (Stream, error)
Publish(ctx context.Context, msg Message, opts ...PublishOption) error Publish(ctx context.Context, msg Message, opts ...PublishOption) error
BatchPublish(ctx context.Context, msg []Message, opts ...PublishOption) error
String() string String() string
} }

View File

@@ -4,8 +4,8 @@ import (
"context" "context"
"sort" "sort"
"github.com/unistack-org/micro/v3/errors" "go.unistack.org/micro/v3/errors"
"github.com/unistack-org/micro/v3/router" "go.unistack.org/micro/v3/router"
) )
// LookupFunc is used to lookup routes for a service // LookupFunc is used to lookup routes for a service

View File

@@ -3,10 +3,10 @@ package client
import ( import (
"context" "context"
"github.com/unistack-org/micro/v3/broker" "go.unistack.org/micro/v3/broker"
"github.com/unistack-org/micro/v3/codec" "go.unistack.org/micro/v3/codec"
"github.com/unistack-org/micro/v3/errors" "go.unistack.org/micro/v3/errors"
"github.com/unistack-org/micro/v3/metadata" "go.unistack.org/micro/v3/metadata"
) )
// DefaultCodecs will be used to encode/decode data // DefaultCodecs will be used to encode/decode data
@@ -173,7 +173,7 @@ func (n *noopClient) NewRequest(service, endpoint string, req interface{}, opts
} }
func (n *noopClient) NewMessage(topic string, msg interface{}, opts ...MessageOption) Message { func (n *noopClient) NewMessage(topic string, msg interface{}, opts ...MessageOption) Message {
options := NewMessageOptions(opts...) options := NewMessageOptions(append([]MessageOption{MessageContentType(n.opts.ContentType)}, opts...)...)
return &noopMessage{topic: topic, payload: msg, opts: options} return &noopMessage{topic: topic, payload: msg, opts: options}
} }
@@ -181,47 +181,59 @@ func (n *noopClient) Stream(ctx context.Context, req Request, opts ...CallOption
return &noopStream{}, nil return &noopStream{}, nil
} }
func (n *noopClient) Publish(ctx context.Context, p Message, opts ...PublishOption) error { func (n *noopClient) BatchPublish(ctx context.Context, ps []Message, opts ...PublishOption) error {
var body []byte return n.publish(ctx, ps, opts...)
}
func (n *noopClient) Publish(ctx context.Context, p Message, opts ...PublishOption) error {
return n.publish(ctx, []Message{p}, opts...)
}
func (n *noopClient) publish(ctx context.Context, ps []Message, opts ...PublishOption) error {
options := NewPublishOptions(opts...) options := NewPublishOptions(opts...)
md, ok := metadata.FromOutgoingContext(ctx) msgs := make([]*broker.Message, 0, len(ps))
if !ok {
md = metadata.New(0)
}
md["Content-Type"] = p.ContentType()
md["Micro-Topic"] = p.Topic()
// passed in raw data for _, p := range ps {
if d, ok := p.Payload().(*codec.Frame); ok { md, ok := metadata.FromOutgoingContext(ctx)
body = d.Data if !ok {
} else { md = metadata.New(0)
// use codec for payload }
cf, err := n.newCodec(p.ContentType()) md[metadata.HeaderContentType] = p.ContentType()
if err != nil {
return errors.InternalServerError("go.micro.client", err.Error()) topic := p.Topic()
// get the exchange
if len(options.Exchange) > 0 {
topic = options.Exchange
} }
// set the body md[metadata.HeaderTopic] = topic
b, err := cf.Marshal(p.Payload())
if err != nil { var body []byte
return errors.InternalServerError("go.micro.client", err.Error())
// passed in raw data
if d, ok := p.Payload().(*codec.Frame); ok {
body = d.Data
} else {
// use codec for payload
cf, err := n.newCodec(p.ContentType())
if err != nil {
return errors.InternalServerError("go.micro.client", err.Error())
}
// set the body
b, err := cf.Marshal(p.Payload())
if err != nil {
return errors.InternalServerError("go.micro.client", err.Error())
}
body = b
} }
body = b
msgs = append(msgs, &broker.Message{Header: md, Body: body})
} }
topic := p.Topic() return n.opts.Broker.BatchPublish(ctx, msgs,
// get the exchange
if len(options.Exchange) > 0 {
topic = options.Exchange
}
return n.opts.Broker.Publish(ctx, topic, &broker.Message{
Header: md,
Body: body,
},
broker.PublishContext(options.Context), broker.PublishContext(options.Context),
broker.PublishBodyOnly(options.BodyOnly), broker.PublishBodyOnly(options.BodyOnly),
) )

View File

@@ -5,20 +5,22 @@ import (
"crypto/tls" "crypto/tls"
"time" "time"
"github.com/unistack-org/micro/v3/broker" "go.unistack.org/micro/v3/broker"
"github.com/unistack-org/micro/v3/codec" "go.unistack.org/micro/v3/codec"
"github.com/unistack-org/micro/v3/logger" "go.unistack.org/micro/v3/logger"
"github.com/unistack-org/micro/v3/meter" "go.unistack.org/micro/v3/meter"
"github.com/unistack-org/micro/v3/network/transport" "go.unistack.org/micro/v3/network/transport"
"github.com/unistack-org/micro/v3/register" "go.unistack.org/micro/v3/register"
"github.com/unistack-org/micro/v3/router" "go.unistack.org/micro/v3/router"
"github.com/unistack-org/micro/v3/selector" "go.unistack.org/micro/v3/selector"
"github.com/unistack-org/micro/v3/selector/random" "go.unistack.org/micro/v3/selector/random"
"github.com/unistack-org/micro/v3/tracer" "go.unistack.org/micro/v3/tracer"
) )
// Options holds client options // Options holds client options
type Options struct { type Options struct {
// Transport used for transfer messages
Transport transport.Transport
// Selector used to select needed address // Selector used to select needed address
Selector selector.Selector Selector selector.Selector
// Logger used to log messages // Logger used to log messages
@@ -29,18 +31,16 @@ type Options struct {
Broker broker.Broker Broker broker.Broker
// Meter used for metrics // Meter used for metrics
Meter meter.Meter Meter meter.Meter
// Router used to get route
Router router.Router
// Transport used for transfer messages
Transport transport.Transport
// Context is used for external options // Context is used for external options
Context context.Context Context context.Context
// Lookup func used to get destination addr // Router used to get route
Lookup LookupFunc Router router.Router
// Codecs map
Codecs map[string]codec.Codec
// TLSConfig specifies tls.Config for secure connection // TLSConfig specifies tls.Config for secure connection
TLSConfig *tls.Config TLSConfig *tls.Config
// Codecs map
Codecs map[string]codec.Codec
// Lookup func used to get destination addr
Lookup LookupFunc
// Proxy is used for proxy requests // Proxy is used for proxy requests
Proxy string Proxy string
// ContentType is used to select codec // ContentType is used to select codec
@@ -68,12 +68,12 @@ func NewCallOptions(opts ...CallOption) CallOptions {
// CallOptions holds client call options // CallOptions holds client call options
type CallOptions struct { type CallOptions struct {
// Router used for route
Router router.Router
// Selector selects addr // Selector selects addr
Selector selector.Selector Selector selector.Selector
// Context used for deadline // Context used for deadline
Context context.Context Context context.Context
// Router used for route
Router router.Router
// Retry func used for retries // Retry func used for retries
Retry RetryFunc Retry RetryFunc
// Backoff func used for backoff when retry // Backoff func used for backoff when retry
@@ -82,22 +82,22 @@ type CallOptions struct {
Network string Network string
// Content-Type // Content-Type
ContentType string ContentType string
// CallWrappers call wrappers // AuthToken string
CallWrappers []CallWrapper AuthToken string
// SelectOptions selector options
SelectOptions []selector.SelectOption
// Address specifies static addr list // Address specifies static addr list
Address []string Address []string
// Retries specifies retries num // SelectOptions selector options
Retries int SelectOptions []selector.SelectOption
// CallWrappers call wrappers
CallWrappers []CallWrapper
// StreamTimeout stream timeout // StreamTimeout stream timeout
StreamTimeout time.Duration StreamTimeout time.Duration
// RequestTimeout request timeout // RequestTimeout request timeout
RequestTimeout time.Duration RequestTimeout time.Duration
// DialTimeout dial timeout // DialTimeout dial timeout
DialTimeout time.Duration DialTimeout time.Duration
// AuthToken string // Retries specifies retries num
AuthToken string Retries int
} }
// Context pass context to client // Context pass context to client
@@ -118,12 +118,12 @@ func NewPublishOptions(opts ...PublishOption) PublishOptions {
// PublishOptions holds publish options // PublishOptions holds publish options
type PublishOptions struct { type PublishOptions struct {
// BodyOnly will publish only message body
BodyOnly bool
// Context used for external options // Context used for external options
Context context.Context Context context.Context
// Exchange topic exchange name // Exchange topic exchange name
Exchange string Exchange string
// BodyOnly will publish only message body
BodyOnly bool
} }
// NewMessageOptions creates message options struct // NewMessageOptions creates message options struct
@@ -267,7 +267,7 @@ func Transport(t transport.Transport) Option {
func Register(r register.Register) Option { func Register(r register.Register) Option {
return func(o *Options) { return func(o *Options) {
if o.Router != nil { if o.Router != nil {
o.Router.Init(router.Register(r)) _ = o.Router.Init(router.Register(r))
} }
} }
} }
@@ -331,7 +331,7 @@ func TLSConfig(t *tls.Config) Option {
// already set. Required for Init call below. // already set. Required for Init call below.
// set the transport tls // set the transport tls
o.Transport.Init( _ = o.Transport.Init(
transport.TLSConfig(t), transport.TLSConfig(t),
) )
} }
@@ -373,19 +373,35 @@ func DialTimeout(d time.Duration) Option {
} }
// WithExchange sets the exchange to route a message through // WithExchange sets the exchange to route a message through
// Deprecated
func WithExchange(e string) PublishOption { func WithExchange(e string) PublishOption {
return func(o *PublishOptions) { return func(o *PublishOptions) {
o.Exchange = e o.Exchange = e
} }
} }
// PublishExchange sets the exchange to route a message through
func PublishExchange(e string) PublishOption {
return func(o *PublishOptions) {
o.Exchange = e
}
}
// WithBodyOnly publish only message body // WithBodyOnly publish only message body
// DERECATED
func WithBodyOnly(b bool) PublishOption { func WithBodyOnly(b bool) PublishOption {
return func(o *PublishOptions) { return func(o *PublishOptions) {
o.BodyOnly = b o.BodyOnly = b
} }
} }
// PublishBodyOnly publish only message body
func PublishBodyOnly(b bool) PublishOption {
return func(o *PublishOptions) {
o.BodyOnly = b
}
}
// PublishContext sets the context in publish options // PublishContext sets the context in publish options
func PublishContext(ctx context.Context) PublishOption { func PublishContext(ctx context.Context) PublishOption {
return func(o *PublishOptions) { return func(o *PublishOptions) {
@@ -498,12 +514,20 @@ func WithSelectOptions(sops ...selector.SelectOption) CallOption {
} }
// WithMessageContentType sets the message content type // WithMessageContentType sets the message content type
// Deprecated
func WithMessageContentType(ct string) MessageOption { func WithMessageContentType(ct string) MessageOption {
return func(o *MessageOptions) { return func(o *MessageOptions) {
o.ContentType = ct o.ContentType = ct
} }
} }
// MessageContentType sets the message content type
func MessageContentType(ct string) MessageOption {
return func(o *MessageOptions) {
o.ContentType = ct
}
}
// StreamingRequest specifies that request is streaming // StreamingRequest specifies that request is streaming
func StreamingRequest(b bool) RequestOption { func StreamingRequest(b bool) RequestOption {
return func(o *RequestOptions) { return func(o *RequestOptions) {

View File

@@ -3,7 +3,7 @@ package client
import ( import (
"context" "context"
"github.com/unistack-org/micro/v3/errors" "go.unistack.org/micro/v3/errors"
) )
// RetryFunc that returning either false or a non-nil error will result in the call not being retried // RetryFunc that returning either false or a non-nil error will result in the call not being retried

View File

@@ -1,7 +1,7 @@
package client package client
import ( import (
"github.com/unistack-org/micro/v3/codec" "go.unistack.org/micro/v3/codec"
) )
type testRequest struct { type testRequest struct {

View File

@@ -1,11 +1,11 @@
// Package codec is an interface for encoding messages // Package codec is an interface for encoding messages
package codec package codec // import "go.unistack.org/micro/v3/codec"
import ( import (
"errors" "errors"
"io" "io"
"github.com/unistack-org/micro/v3/metadata" "go.unistack.org/micro/v3/metadata"
) )
// Message types // Message types
@@ -25,7 +25,7 @@ var (
var ( var (
// DefaultMaxMsgSize specifies how much data codec can handle // DefaultMaxMsgSize specifies how much data codec can handle
DefaultMaxMsgSize int = 1024 * 1024 * 4 // 4Mb DefaultMaxMsgSize = 1024 * 1024 * 4 // 4Mb
// DefaultCodec is the global default codec // DefaultCodec is the global default codec
DefaultCodec Codec = NewCodec() DefaultCodec Codec = NewCodec()
// DefaultTagName specifies struct tag name to control codec Marshal/Unmarshal // DefaultTagName specifies struct tag name to control codec Marshal/Unmarshal
@@ -41,11 +41,11 @@ type MessageType int
// connection. ReadBody may be called with a nil argument to force the // connection. ReadBody may be called with a nil argument to force the
// body to be read and discarded. // body to be read and discarded.
type Codec interface { type Codec interface {
ReadHeader(io.Reader, *Message, MessageType) error ReadHeader(r io.Reader, m *Message, mt MessageType) error
ReadBody(io.Reader, interface{}) error ReadBody(r io.Reader, v interface{}) error
Write(io.Writer, *Message, interface{}) error Write(w io.Writer, m *Message, v interface{}) error
Marshal(interface{}) ([]byte, error) Marshal(v interface{}, opts ...Option) ([]byte, error)
Unmarshal([]byte, interface{}) error Unmarshal(b []byte, v interface{}, opts ...Option) error
String() string String() string
} }
@@ -58,7 +58,7 @@ type Message struct {
Method string Method string
Endpoint string Endpoint string
Error string Error string
Id string ID string
Body []byte Body []byte
Type MessageType Type MessageType
} }
@@ -67,3 +67,20 @@ type Message struct {
func NewMessage(t MessageType) *Message { func NewMessage(t MessageType) *Message {
return &Message{Type: t, Header: metadata.New(0)} return &Message{Type: t, Header: metadata.New(0)}
} }
// MarshalAppend calls codec.Marshal(v) and returns the data appended to buf.
// If codec implements MarshalAppend, that is called instead.
func MarshalAppend(buf []byte, c Codec, v interface{}, opts ...Option) ([]byte, error) {
if nc, ok := c.(interface {
MarshalAppend([]byte, interface{}, ...Option) ([]byte, error)
}); ok {
return nc.MarshalAppend(buf, v, opts...)
}
mbuf, err := c.Marshal(v, opts...)
if err != nil {
return nil, err
}
return append(buf, mbuf...), nil
}

34
codec/context.go Normal file
View File

@@ -0,0 +1,34 @@
package codec
import (
"context"
)
type codecKey struct{}
// FromContext returns codec from context
func FromContext(ctx context.Context) (Codec, bool) {
if ctx == nil {
return nil, false
}
c, ok := ctx.Value(codecKey{}).(Codec)
return c, ok
}
// NewContext put codec in context
func NewContext(ctx context.Context, c Codec) context.Context {
if ctx == nil {
ctx = context.Background()
}
return context.WithValue(ctx, codecKey{}, c)
}
// SetOption returns a function to setup a context with given value
func SetOption(k, v interface{}) Option {
return func(o *Options) {
if o.Context == nil {
o.Context = context.Background()
}
o.Context = context.WithValue(o.Context, k, v)
}
}

View File

@@ -4,3 +4,31 @@ package codec
type Frame struct { type Frame struct {
Data []byte Data []byte
} }
func (m *Frame) MarshalJSON() ([]byte, error) {
return m.Data, nil
}
func (m *Frame) UnmarshalJSON(data []byte) error {
m.Data = data
return nil
}
func (m *Frame) ProtoMessage() {}
func (m *Frame) Reset() {
*m = Frame{}
}
func (m *Frame) String() string {
return string(m.Data)
}
func (m *Frame) Marshal() ([]byte, error) {
return m.Data, nil
}
func (m *Frame) Unmarshal(data []byte) error {
m.Data = data
return nil
}

View File

@@ -17,7 +17,7 @@ syntax = "proto3";
package micro.codec; package micro.codec;
option cc_enable_arenas = true; option cc_enable_arenas = true;
option go_package = "github.com/unistack-org/micro/v3/codec;codec"; option go_package = "go.unistack.org/micro/v3/codec;codec";
option java_multiple_files = true; option java_multiple_files = true;
option java_outer_classname = "MicroCodec"; option java_outer_classname = "MicroCodec";
option java_package = "micro.codec"; option java_package = "micro.codec";

View File

@@ -5,7 +5,9 @@ import (
"io" "io"
) )
type noopCodec struct{} type noopCodec struct {
opts Options
}
func (c *noopCodec) ReadHeader(conn io.Reader, m *Message, t MessageType) error { func (c *noopCodec) ReadHeader(conn io.Reader, m *Message, t MessageType) error {
return nil return nil
@@ -69,11 +71,11 @@ func (c *noopCodec) String() string {
} }
// NewCodec returns new noop codec // NewCodec returns new noop codec
func NewCodec() Codec { func NewCodec(opts ...Option) Codec {
return &noopCodec{} return &noopCodec{opts: NewOptions(opts...)}
} }
func (c *noopCodec) Marshal(v interface{}) ([]byte, error) { func (c *noopCodec) Marshal(v interface{}, opts ...Option) ([]byte, error) {
if v == nil { if v == nil {
return nil, nil return nil, nil
} }
@@ -96,7 +98,7 @@ func (c *noopCodec) Marshal(v interface{}) ([]byte, error) {
return json.Marshal(v) return json.Marshal(v)
} }
func (c *noopCodec) Unmarshal(d []byte, v interface{}) error { func (c *noopCodec) Unmarshal(d []byte, v interface{}, opts ...Option) error {
if v == nil { if v == nil {
return nil return nil
} }

View File

@@ -1,9 +1,11 @@
package codec package codec
import ( import (
"github.com/unistack-org/micro/v3/logger" "context"
"github.com/unistack-org/micro/v3/meter"
"github.com/unistack-org/micro/v3/tracer" "go.unistack.org/micro/v3/logger"
"go.unistack.org/micro/v3/meter"
"go.unistack.org/micro/v3/tracer"
) )
// Option func // Option func
@@ -17,6 +19,10 @@ type Options struct {
Logger logger.Logger Logger logger.Logger
// Tracer used for tracing // Tracer used for tracing
Tracer tracer.Tracer Tracer tracer.Tracer
// Context stores additional codec options
Context context.Context
// TagName specifies tag name in struct to control codec
TagName string
// MaxMsgSize specifies max messages size that reads by codec // MaxMsgSize specifies max messages size that reads by codec
MaxMsgSize int MaxMsgSize int
} }
@@ -28,6 +34,13 @@ func MaxMsgSize(n int) Option {
} }
} }
// TagName sets the codec tag name in struct
func TagName(n string) Option {
return func(o *Options) {
o.TagName = n
}
}
// Logger sets the logger // Logger sets the logger
func Logger(l logger.Logger) Option { func Logger(l logger.Logger) Option {
return func(o *Options) { return func(o *Options) {
@@ -52,10 +65,12 @@ func Meter(m meter.Meter) Option {
// NewOptions returns new options // NewOptions returns new options
func NewOptions(opts ...Option) Options { func NewOptions(opts ...Option) Options {
options := Options{ options := Options{
Context: context.Background(),
Logger: logger.DefaultLogger, Logger: logger.DefaultLogger,
Meter: meter.DefaultMeter, Meter: meter.DefaultMeter,
Tracer: tracer.DefaultTracer, Tracer: tracer.DefaultTracer,
MaxMsgSize: DefaultMaxMsgSize, MaxMsgSize: DefaultMaxMsgSize,
TagName: DefaultTagName,
} }
for _, o := range opts { for _, o := range opts {

View File

@@ -1,14 +1,21 @@
// Package config is an interface for dynamic configuration. // Package config is an interface for dynamic configuration.
package config package config // import "go.unistack.org/micro/v3/config"
import ( import (
"context" "context"
"errors" "errors"
"time"
) )
// DefaultConfig default config // DefaultConfig default config
var DefaultConfig Config = NewConfig() var DefaultConfig Config = NewConfig()
// DefaultWatcherMinInterval default min interval for poll changes
var DefaultWatcherMinInterval = 5 * time.Second
// DefaultWatcherMaxInterval default max interval for poll changes
var DefaultWatcherMaxInterval = 9 * time.Second
var ( var (
// ErrCodecMissing is returned when codec needed and not specified // ErrCodecMissing is returned when codec needed and not specified
ErrCodecMissing = errors.New("codec missing") ErrCodecMissing = errors.New("codec missing")
@@ -30,15 +37,17 @@ type Config interface {
Load(context.Context, ...LoadOption) error Load(context.Context, ...LoadOption) error
// Save config to sources // Save config to sources
Save(context.Context, ...SaveOption) error Save(context.Context, ...SaveOption) error
// Watch a value for changes // Watch a config for changes
//Watch(context.Context) (Watcher, error) Watch(context.Context, ...WatchOption) (Watcher, error)
// String returns config type name // String returns config type name
String() string String() string
} }
// Watcher is the config watcher // Watcher is the config watcher
type Watcher interface { type Watcher interface {
// Next() (, error) // Next blocks until update happens or error returned
Next() (map[string]interface{}, error)
// Stop stops watcher
Stop() error Stop() error
} }
@@ -55,3 +64,53 @@ func Load(ctx context.Context, cs []Config, opts ...LoadOption) error {
} }
return nil return nil
} }
var (
DefaultAfterLoad = func(ctx context.Context, c Config) error {
for _, fn := range c.Options().AfterLoad {
if err := fn(ctx, c); err != nil {
c.Options().Logger.Errorf(ctx, "%s AfterLoad err: %v", c.String(), err)
if !c.Options().AllowFail {
return err
}
}
}
return nil
}
DefaultAfterSave = func(ctx context.Context, c Config) error {
for _, fn := range c.Options().AfterSave {
if err := fn(ctx, c); err != nil {
c.Options().Logger.Errorf(ctx, "%s AfterSave err: %v", c.String(), err)
if !c.Options().AllowFail {
return err
}
}
}
return nil
}
DefaultBeforeLoad = func(ctx context.Context, c Config) error {
for _, fn := range c.Options().BeforeLoad {
if err := fn(ctx, c); err != nil {
c.Options().Logger.Errorf(ctx, "%s BeforeLoad err: %v", c.String(), err)
if !c.Options().AllowFail {
return err
}
}
}
return nil
}
DefaultBeforeSave = func(ctx context.Context, c Config) error {
for _, fn := range c.Options().BeforeSave {
if err := fn(ctx, c); err != nil {
c.Options().Logger.Errorf(ctx, "%s BeforeSavec err: %v", c.String(), err)
if !c.Options().AllowFail {
return err
}
}
}
return nil
}
)

View File

@@ -2,12 +2,13 @@ package config
import ( import (
"context" "context"
"fmt"
"reflect" "reflect"
"strconv" "strconv"
"strings" "strings"
"github.com/imdario/mergo" "github.com/imdario/mergo"
rutil "github.com/unistack-org/micro/v3/util/reflect" rutil "go.unistack.org/micro/v3/util/reflect"
) )
type defaultConfig struct { type defaultConfig struct {
@@ -26,10 +27,8 @@ func (c *defaultConfig) Init(opts ...Option) error {
} }
func (c *defaultConfig) Load(ctx context.Context, opts ...LoadOption) error { func (c *defaultConfig) Load(ctx context.Context, opts ...LoadOption) error {
for _, fn := range c.opts.BeforeLoad { if err := DefaultBeforeLoad(ctx, c); err != nil {
if err := fn(ctx, c); err != nil && !c.opts.AllowFail { return err
return err
}
} }
options := NewLoadOptions(opts...) options := NewLoadOptions(opts...)
@@ -41,29 +40,39 @@ func (c *defaultConfig) Load(ctx context.Context, opts ...LoadOption) error {
mopts = append(mopts, mergo.WithAppendSlice) mopts = append(mopts, mergo.WithAppendSlice)
} }
src, err := rutil.Zero(c.opts.Struct) dst := c.opts.Struct
if err == nil { if options.Struct != nil {
valueOf := reflect.ValueOf(src) dst = options.Struct
if err = c.fillValues(valueOf); err == nil {
err = mergo.Merge(c.opts.Struct, src, mopts...)
}
} }
if err != nil && !c.opts.AllowFail { src, err := rutil.Zero(dst)
return err if err != nil {
} if !c.opts.AllowFail {
for _, fn := range c.opts.AfterLoad {
if err := fn(ctx, c); err != nil && !c.opts.AllowFail {
return err return err
} }
return DefaultAfterLoad(ctx, c)
}
if err = fillValues(reflect.ValueOf(src), c.opts.StructTag); err == nil {
err = mergo.Merge(dst, src, mopts...)
}
if err != nil {
c.opts.Logger.Errorf(ctx, "default load error: %v", err)
if !c.opts.AllowFail {
return err
}
}
if err := DefaultAfterLoad(ctx, c); err != nil {
return err
} }
return nil return nil
} }
//nolint:gocyclo //nolint:gocyclo
func (c *defaultConfig) fillValue(value reflect.Value, val string) error { func fillValue(value reflect.Value, val string) error {
if !rutil.IsEmpty(value) { if !rutil.IsEmpty(value) {
return nil return nil
} }
@@ -80,10 +89,10 @@ func (c *defaultConfig) fillValue(value reflect.Value, val string) error {
kv := strings.FieldsFunc(nval, func(c rune) bool { return c == '=' }) kv := strings.FieldsFunc(nval, func(c rune) bool { return c == '=' })
mkey := reflect.Indirect(reflect.New(kt)) mkey := reflect.Indirect(reflect.New(kt))
mval := reflect.Indirect(reflect.New(et)) mval := reflect.Indirect(reflect.New(et))
if err := c.fillValue(mkey, kv[0]); err != nil { if err := fillValue(mkey, kv[0]); err != nil {
return err return err
} }
if err := c.fillValue(mval, kv[1]); err != nil { if err := fillValue(mval, kv[1]); err != nil {
return err return err
} }
value.SetMapIndex(mkey, mval) value.SetMapIndex(mkey, mval)
@@ -93,7 +102,7 @@ func (c *defaultConfig) fillValue(value reflect.Value, val string) error {
value.Set(reflect.MakeSlice(reflect.SliceOf(value.Type().Elem()), len(nvals), len(nvals))) value.Set(reflect.MakeSlice(reflect.SliceOf(value.Type().Elem()), len(nvals), len(nvals)))
for idx, nval := range nvals { for idx, nval := range nvals {
nvalue := reflect.Indirect(reflect.New(value.Type().Elem())) nvalue := reflect.Indirect(reflect.New(value.Type().Elem()))
if err := c.fillValue(nvalue, nval); err != nil { if err := fillValue(nvalue, nval); err != nil {
return err return err
} }
value.Index(idx).Set(nvalue) value.Index(idx).Set(nvalue)
@@ -182,7 +191,7 @@ func (c *defaultConfig) fillValue(value reflect.Value, val string) error {
return nil return nil
} }
func (c *defaultConfig) fillValues(valueOf reflect.Value) error { func fillValues(valueOf reflect.Value, tname string) error {
var values reflect.Value var values reflect.Value
if valueOf.Kind() == reflect.Ptr { if valueOf.Kind() == reflect.Ptr {
@@ -209,7 +218,7 @@ func (c *defaultConfig) fillValues(valueOf reflect.Value) error {
switch value.Kind() { switch value.Kind() {
case reflect.Struct: case reflect.Struct:
value.Set(reflect.Indirect(reflect.New(value.Type()))) value.Set(reflect.Indirect(reflect.New(value.Type())))
if err := c.fillValues(value); err != nil { if err := fillValues(value, tname); err != nil {
return err return err
} }
continue continue
@@ -223,17 +232,17 @@ func (c *defaultConfig) fillValues(valueOf reflect.Value) error {
value.Set(reflect.New(value.Type().Elem())) value.Set(reflect.New(value.Type().Elem()))
} }
value = value.Elem() value = value.Elem()
if err := c.fillValues(value); err != nil { if err := fillValues(value, tname); err != nil {
return err return err
} }
continue continue
} }
tag, ok := field.Tag.Lookup(c.opts.StructTag) tag, ok := field.Tag.Lookup(tname)
if !ok { if !ok {
continue continue
} }
if err := c.fillValue(value, tag); err != nil { if err := fillValue(value, tag); err != nil {
return err return err
} }
} }
@@ -242,16 +251,12 @@ func (c *defaultConfig) fillValues(valueOf reflect.Value) error {
} }
func (c *defaultConfig) Save(ctx context.Context, opts ...SaveOption) error { func (c *defaultConfig) Save(ctx context.Context, opts ...SaveOption) error {
for _, fn := range c.opts.BeforeSave { if err := DefaultBeforeSave(ctx, c); err != nil {
if err := fn(ctx, c); err != nil && !c.opts.AllowFail { return err
return err
}
} }
for _, fn := range c.opts.AfterSave { if err := DefaultAfterSave(ctx, c); err != nil {
if err := fn(ctx, c); err != nil && !c.opts.AllowFail { return err
return err
}
} }
return nil return nil
@@ -265,6 +270,10 @@ func (c *defaultConfig) Name() string {
return c.opts.Name return c.opts.Name
} }
func (c *defaultConfig) Watch(ctx context.Context, opts ...WatchOption) (Watcher, error) {
return nil, fmt.Errorf("not implemented")
}
// NewConfig returns new default config source // NewConfig returns new default config source
func NewConfig(opts ...Option) Config { func NewConfig(opts ...Option) Config {
options := NewOptions(opts...) options := NewOptions(opts...)

View File

@@ -5,7 +5,7 @@ import (
"fmt" "fmt"
"testing" "testing"
"github.com/unistack-org/micro/v3/config" "go.unistack.org/micro/v3/config"
) )
type Cfg struct { type Cfg struct {
@@ -47,6 +47,6 @@ func TestDefault(t *testing.T) {
if conf.StringValue != "after_load" { if conf.StringValue != "after_load" {
t.Fatal("AfterLoad option not working") t.Fatal("AfterLoad option not working")
} }
_ = conf
t.Logf("%#+v\n", conf) // t.Logf("%#+v\n", conf)
} }

View File

@@ -2,11 +2,12 @@ package config
import ( import (
"context" "context"
"time"
"github.com/unistack-org/micro/v3/codec" "go.unistack.org/micro/v3/codec"
"github.com/unistack-org/micro/v3/logger" "go.unistack.org/micro/v3/logger"
"github.com/unistack-org/micro/v3/meter" "go.unistack.org/micro/v3/meter"
"github.com/unistack-org/micro/v3/tracer" "go.unistack.org/micro/v3/tracer"
) )
// Options hold the config options // Options hold the config options
@@ -62,6 +63,7 @@ type LoadOption func(o *LoadOptions)
// LoadOptions struct // LoadOptions struct
type LoadOptions struct { type LoadOptions struct {
Struct interface{}
Override bool Override bool
Append bool Append bool
} }
@@ -88,13 +90,29 @@ func LoadAppend(b bool) LoadOption {
} }
} }
// LoadStruct override struct for loading
func LoadStruct(src interface{}) LoadOption {
return func(o *LoadOptions) {
o.Struct = src
}
}
// SaveOption function signature // SaveOption function signature
type SaveOption func(o *SaveOptions) type SaveOption func(o *SaveOptions)
// SaveOptions struct // SaveOptions struct
type SaveOptions struct { type SaveOptions struct {
Struct interface{}
} }
// SaveStruct override struct for save to config
func SaveStruct(src interface{}) SaveOption {
return func(o *SaveOptions) {
o.Struct = src
}
}
// NewSaveOptions fill SaveOptions struct
func NewSaveOptions(opts ...SaveOption) SaveOptions { func NewSaveOptions(opts ...SaveOption) SaveOptions {
options := SaveOptions{} options := SaveOptions{}
for _, o := range opts { for _, o := range opts {
@@ -186,3 +204,60 @@ func Name(n string) Option {
o.Name = n o.Name = n
} }
} }
// WatchOptions struuct
type WatchOptions struct {
// Context used by non default options
Context context.Context
// Struct for filling
Struct interface{}
// MinInterval specifies the min time.Duration interval for poll changes
MinInterval time.Duration
// MaxInterval specifies the max time.Duration interval for poll changes
MaxInterval time.Duration
// Coalesce multiple events to one
Coalesce bool
}
type WatchOption func(*WatchOptions)
func NewWatchOptions(opts ...WatchOption) WatchOptions {
options := WatchOptions{
Context: context.Background(),
MinInterval: DefaultWatcherMinInterval,
MaxInterval: DefaultWatcherMaxInterval,
}
for _, o := range opts {
o(&options)
}
return options
}
// WatchContext pass context
func WatchContext(ctx context.Context) WatchOption {
return func(o *WatchOptions) {
o.Context = ctx
}
}
// WatchCoalesce controls watch event combining
func WatchCoalesce(b bool) WatchOption {
return func(o *WatchOptions) {
o.Coalesce = b
}
}
// WatchInterval specifies min and max time.Duration for pulling changes
func WatchInterval(min, max time.Duration) WatchOption {
return func(o *WatchOptions) {
o.MinInterval = min
o.MaxInterval = max
}
}
// WatchStruct overrides struct for fill
func WatchStruct(src interface{}) WatchOption {
return func(o *WatchOptions) {
o.Struct = src
}
}

View File

@@ -1,26 +0,0 @@
package config_test
import (
"testing"
rutil "github.com/unistack-org/micro/v3/util/reflect"
)
type Config struct {
SubConfig *SubConfig
Config *Config
Value string
}
type SubConfig struct {
Value string
}
func TestReflect(t *testing.T) {
cfg1 := &Config{Value: "cfg1", Config: &Config{Value: "cfg1_1"}, SubConfig: &SubConfig{Value: "cfg1"}}
cfg2, err := rutil.Zero(cfg1)
if err != nil {
t.Fatal(err)
}
t.Logf("dst: %#+v\n", cfg2)
}

View File

@@ -1,6 +1,6 @@
// Package errors provides a way to return detailed information // Package errors provides a way to return detailed information
// for an RPC request error. The error is normally JSON encoded. // for an RPC request error. The error is normally JSON encoded.
package errors package errors // import "go.unistack.org/micro/v3/errors"
import ( import (
"encoding/json" "encoding/json"
@@ -37,8 +37,8 @@ var (
// Error type // Error type
type Error struct { type Error struct {
// Id holds error id or service, usually someting like my_service or uuid // ID holds error id or service, usually someting like my_service or id
Id string ID string
// Detail holds some useful details about error // Detail holds some useful details about error
Detail string Detail string
// Status usually holds text of http status // Status usually holds text of http status
@@ -56,7 +56,7 @@ func (e *Error) Error() string {
// New generates a custom error // New generates a custom error
func New(id, detail string, code int32) error { func New(id, detail string, code int32) error {
return &Error{ return &Error{
Id: id, ID: id,
Code: code, Code: code,
Detail: detail, Detail: detail,
Status: http.StatusText(int(code)), Status: http.StatusText(int(code)),
@@ -77,7 +77,7 @@ func Parse(err string) *Error {
// BadRequest generates a 400 error. // BadRequest generates a 400 error.
func BadRequest(id, format string, a ...interface{}) error { func BadRequest(id, format string, a ...interface{}) error {
return &Error{ return &Error{
Id: id, ID: id,
Code: 400, Code: 400,
Detail: fmt.Sprintf(format, a...), Detail: fmt.Sprintf(format, a...),
Status: http.StatusText(400), Status: http.StatusText(400),
@@ -87,7 +87,7 @@ func BadRequest(id, format string, a ...interface{}) error {
// Unauthorized generates a 401 error. // Unauthorized generates a 401 error.
func Unauthorized(id, format string, a ...interface{}) error { func Unauthorized(id, format string, a ...interface{}) error {
return &Error{ return &Error{
Id: id, ID: id,
Code: 401, Code: 401,
Detail: fmt.Sprintf(format, a...), Detail: fmt.Sprintf(format, a...),
Status: http.StatusText(401), Status: http.StatusText(401),
@@ -97,7 +97,7 @@ func Unauthorized(id, format string, a ...interface{}) error {
// Forbidden generates a 403 error. // Forbidden generates a 403 error.
func Forbidden(id, format string, a ...interface{}) error { func Forbidden(id, format string, a ...interface{}) error {
return &Error{ return &Error{
Id: id, ID: id,
Code: 403, Code: 403,
Detail: fmt.Sprintf(format, a...), Detail: fmt.Sprintf(format, a...),
Status: http.StatusText(403), Status: http.StatusText(403),
@@ -107,7 +107,7 @@ func Forbidden(id, format string, a ...interface{}) error {
// NotFound generates a 404 error. // NotFound generates a 404 error.
func NotFound(id, format string, a ...interface{}) error { func NotFound(id, format string, a ...interface{}) error {
return &Error{ return &Error{
Id: id, ID: id,
Code: 404, Code: 404,
Detail: fmt.Sprintf(format, a...), Detail: fmt.Sprintf(format, a...),
Status: http.StatusText(404), Status: http.StatusText(404),
@@ -117,7 +117,7 @@ func NotFound(id, format string, a ...interface{}) error {
// MethodNotAllowed generates a 405 error. // MethodNotAllowed generates a 405 error.
func MethodNotAllowed(id, format string, a ...interface{}) error { func MethodNotAllowed(id, format string, a ...interface{}) error {
return &Error{ return &Error{
Id: id, ID: id,
Code: 405, Code: 405,
Detail: fmt.Sprintf(format, a...), Detail: fmt.Sprintf(format, a...),
Status: http.StatusText(405), Status: http.StatusText(405),
@@ -127,7 +127,7 @@ func MethodNotAllowed(id, format string, a ...interface{}) error {
// Timeout generates a 408 error. // Timeout generates a 408 error.
func Timeout(id, format string, a ...interface{}) error { func Timeout(id, format string, a ...interface{}) error {
return &Error{ return &Error{
Id: id, ID: id,
Code: 408, Code: 408,
Detail: fmt.Sprintf(format, a...), Detail: fmt.Sprintf(format, a...),
Status: http.StatusText(408), Status: http.StatusText(408),
@@ -137,7 +137,7 @@ func Timeout(id, format string, a ...interface{}) error {
// Conflict generates a 409 error. // Conflict generates a 409 error.
func Conflict(id, format string, a ...interface{}) error { func Conflict(id, format string, a ...interface{}) error {
return &Error{ return &Error{
Id: id, ID: id,
Code: 409, Code: 409,
Detail: fmt.Sprintf(format, a...), Detail: fmt.Sprintf(format, a...),
Status: http.StatusText(409), Status: http.StatusText(409),
@@ -147,7 +147,7 @@ func Conflict(id, format string, a ...interface{}) error {
// InternalServerError generates a 500 error. // InternalServerError generates a 500 error.
func InternalServerError(id, format string, a ...interface{}) error { func InternalServerError(id, format string, a ...interface{}) error {
return &Error{ return &Error{
Id: id, ID: id,
Code: 500, Code: 500,
Detail: fmt.Sprintf(format, a...), Detail: fmt.Sprintf(format, a...),
Status: http.StatusText(500), Status: http.StatusText(500),
@@ -157,7 +157,7 @@ func InternalServerError(id, format string, a ...interface{}) error {
// NotImplemented generates a 501 error // NotImplemented generates a 501 error
func NotImplemented(id, format string, a ...interface{}) error { func NotImplemented(id, format string, a ...interface{}) error {
return &Error{ return &Error{
Id: id, ID: id,
Code: 501, Code: 501,
Detail: fmt.Sprintf(format, a...), Detail: fmt.Sprintf(format, a...),
Status: http.StatusText(501), Status: http.StatusText(501),
@@ -167,7 +167,7 @@ func NotImplemented(id, format string, a ...interface{}) error {
// BadGateway generates a 502 error // BadGateway generates a 502 error
func BadGateway(id, format string, a ...interface{}) error { func BadGateway(id, format string, a ...interface{}) error {
return &Error{ return &Error{
Id: id, ID: id,
Code: 502, Code: 502,
Detail: fmt.Sprintf(format, a...), Detail: fmt.Sprintf(format, a...),
Status: http.StatusText(502), Status: http.StatusText(502),
@@ -177,7 +177,7 @@ func BadGateway(id, format string, a ...interface{}) error {
// ServiceUnavailable generates a 503 error // ServiceUnavailable generates a 503 error
func ServiceUnavailable(id, format string, a ...interface{}) error { func ServiceUnavailable(id, format string, a ...interface{}) error {
return &Error{ return &Error{
Id: id, ID: id,
Code: 503, Code: 503,
Detail: fmt.Sprintf(format, a...), Detail: fmt.Sprintf(format, a...),
Status: http.StatusText(503), Status: http.StatusText(503),
@@ -187,7 +187,7 @@ func ServiceUnavailable(id, format string, a ...interface{}) error {
// GatewayTimeout generates a 504 error // GatewayTimeout generates a 504 error
func GatewayTimeout(id, format string, a ...interface{}) error { func GatewayTimeout(id, format string, a ...interface{}) error {
return &Error{ return &Error{
Id: id, ID: id,
Code: 504, Code: 504,
Detail: fmt.Sprintf(format, a...), Detail: fmt.Sprintf(format, a...),
Status: http.StatusText(504), Status: http.StatusText(504),

View File

@@ -9,12 +9,12 @@ import (
func TestFromError(t *testing.T) { func TestFromError(t *testing.T) {
err := NotFound("go.micro.test", "%s", "example") err := NotFound("go.micro.test", "%s", "example")
merr := FromError(err) merr := FromError(err)
if merr.Id != "go.micro.test" || merr.Code != 404 { if merr.ID != "go.micro.test" || merr.Code != 404 {
t.Fatalf("invalid conversation %v != %v", err, merr) t.Fatalf("invalid conversation %v != %v", err, merr)
} }
err = er.New(err.Error()) err = er.New(err.Error())
merr = FromError(err) merr = FromError(err)
if merr.Id != "go.micro.test" || merr.Code != 404 { if merr.ID != "go.micro.test" || merr.Code != 404 {
t.Fatalf("invalid conversation %v != %v", err, merr) t.Fatalf("invalid conversation %v != %v", err, merr)
} }
} }
@@ -36,7 +36,7 @@ func TestEqual(t *testing.T) {
func TestErrors(t *testing.T) { func TestErrors(t *testing.T) {
testData := []*Error{ testData := []*Error{
{ {
Id: "test", ID: "test",
Code: 500, Code: 500,
Detail: "Internal server error", Detail: "Internal server error",
Status: http.StatusText(500), Status: http.StatusText(500),
@@ -44,7 +44,7 @@ func TestErrors(t *testing.T) {
} }
for _, e := range testData { for _, e := range testData {
ne := New(e.Id, e.Detail, e.Code) ne := New(e.ID, e.Detail, e.Code)
if e.Error() != ne.Error() { if e.Error() != ne.Error() {
t.Fatalf("Expected %s got %s", e.Error(), ne.Error()) t.Fatalf("Expected %s got %s", e.Error(), ne.Error())
@@ -56,8 +56,8 @@ func TestErrors(t *testing.T) {
t.Fatalf("Expected error got nil %v", pe) t.Fatalf("Expected error got nil %v", pe)
} }
if pe.Id != e.Id { if pe.ID != e.ID {
t.Fatalf("Expected %s got %s", e.Id, pe.Id) t.Fatalf("Expected %s got %s", e.ID, pe.ID)
} }
if pe.Detail != e.Detail { if pe.Detail != e.Detail {

View File

@@ -3,7 +3,7 @@ package micro
import ( import (
"context" "context"
"github.com/unistack-org/micro/v3/client" "go.unistack.org/micro/v3/client"
) )
// Event is used to publish messages to a topic // Event is used to publish messages to a topic

View File

@@ -3,12 +3,16 @@ package flow
import ( import (
"context" "context"
"fmt" "fmt"
"path/filepath"
"sync" "sync"
"github.com/google/uuid"
"github.com/silas/dag" "github.com/silas/dag"
"github.com/unistack-org/micro/v3/client" "go.unistack.org/micro/v3/client"
"github.com/unistack-org/micro/v3/codec" "go.unistack.org/micro/v3/codec"
"go.unistack.org/micro/v3/logger"
"go.unistack.org/micro/v3/metadata"
"go.unistack.org/micro/v3/store"
"go.unistack.org/micro/v3/util/id"
) )
type microFlow struct { type microFlow struct {
@@ -16,49 +20,94 @@ type microFlow struct {
} }
type microWorkflow struct { type microWorkflow struct {
id string opts Options
g *dag.AcyclicGraph g *dag.AcyclicGraph
init bool steps map[string]Step
id string
status Status
sync.RWMutex sync.RWMutex
opts Options init bool
steps map[string]Step
} }
func (w *microWorkflow) ID() string { func (w *microWorkflow) ID() string {
return w.id return w.id
} }
func (w *microWorkflow) Steps() [][]Step { func (w *microWorkflow) Steps() ([][]Step, error) {
return nil return w.getSteps("", false)
} }
func (w *microWorkflow) AppendSteps(ctx context.Context, steps ...Step) error { func (w *microWorkflow) Status() Status {
return nil return w.status
} }
func (w *microWorkflow) RemoveSteps(ctx context.Context, steps ...Step) error { func (w *microWorkflow) AppendSteps(steps ...Step) error {
return nil
}
func (w *microWorkflow) Execute(ctx context.Context, req interface{}, opts ...ExecuteOption) (string, error) {
w.Lock() w.Lock()
if !w.init {
if err := w.g.Validate(); err != nil { for _, s := range steps {
w.Unlock() w.steps[s.String()] = s
return "", err w.g.Add(s)
}
w.g.TransitiveReduction()
w.init = true
} }
for _, dst := range steps {
for _, req := range dst.Requires() {
src, ok := w.steps[req]
if !ok {
return ErrStepNotExists
}
w.g.Connect(dag.BasicEdge(src, dst))
}
}
if err := w.g.Validate(); err != nil {
w.Unlock()
return err
}
w.g.TransitiveReduction()
w.Unlock() w.Unlock()
uid, err := uuid.NewRandom() return nil
if err != nil { }
return "", err
func (w *microWorkflow) RemoveSteps(steps ...Step) error {
// TODO: handle case when some step requires or required by removed step
w.Lock()
for _, s := range steps {
delete(w.steps, s.String())
w.g.Remove(s)
} }
options := NewExecuteOptions(opts...) for _, dst := range steps {
for _, req := range dst.Requires() {
src, ok := w.steps[req]
if !ok {
return ErrStepNotExists
}
w.g.Connect(dag.BasicEdge(src, dst))
}
}
if err := w.g.Validate(); err != nil {
w.Unlock()
return err
}
w.g.TransitiveReduction()
w.Unlock()
return nil
}
func (w *microWorkflow) getSteps(start string, reverse bool) ([][]Step, error) {
var steps [][]Step var steps [][]Step
var root dag.Vertex
var err error
fn := func(n dag.Vertex, idx int) error { fn := func(n dag.Vertex, idx int) error {
if idx == 0 { if idx == 0 {
steps = make([][]Step, 1) steps = make([][]Step, 1)
@@ -73,59 +122,231 @@ func (w *microWorkflow) Execute(ctx context.Context, req interface{}, opts ...Ex
return nil return nil
} }
var root dag.Vertex if start != "" {
if options.Start != "" {
var ok bool var ok bool
w.RLock() w.RLock()
root, ok = w.steps[options.Start] root, ok = w.steps[start]
w.RUnlock() w.RUnlock()
if !ok { if !ok {
return "", ErrStepNotExists return nil, ErrStepNotExists
} }
} else { } else {
root, err = w.g.Root() root, err = w.g.Root()
if err != nil { if err != nil {
return "", err return nil, err
} }
} }
if options.Reverse {
if reverse {
err = w.g.SortedReverseDepthFirstWalk([]dag.Vertex{root}, fn) err = w.g.SortedReverseDepthFirstWalk([]dag.Vertex{root}, fn)
} else { } else {
err = w.g.SortedDepthFirstWalk([]dag.Vertex{root}, fn) err = w.g.SortedDepthFirstWalk([]dag.Vertex{root}, fn)
} }
if err != nil { if err != nil {
return nil, err
}
return steps, nil
}
func (w *microWorkflow) Abort(ctx context.Context, id string) error {
workflowStore := store.NewNamespaceStore(w.opts.Store, filepath.Join("workflows", id))
return workflowStore.Write(ctx, "status", &codec.Frame{Data: []byte(StatusAborted.String())})
}
func (w *microWorkflow) Suspend(ctx context.Context, id string) error {
workflowStore := store.NewNamespaceStore(w.opts.Store, filepath.Join("workflows", id))
return workflowStore.Write(ctx, "status", &codec.Frame{Data: []byte(StatusSuspend.String())})
}
func (w *microWorkflow) Resume(ctx context.Context, id string) error {
workflowStore := store.NewNamespaceStore(w.opts.Store, filepath.Join("workflows", id))
return workflowStore.Write(ctx, "status", &codec.Frame{Data: []byte(StatusRunning.String())})
}
func (w *microWorkflow) Execute(ctx context.Context, req *Message, opts ...ExecuteOption) (string, error) {
w.Lock()
if !w.init {
if err := w.g.Validate(); err != nil {
w.Unlock()
return "", err
}
w.g.TransitiveReduction()
w.init = true
}
w.Unlock()
eid, err := id.New()
if err != nil {
return "", err
}
stepStore := store.NewNamespaceStore(w.opts.Store, filepath.Join("steps", eid))
workflowStore := store.NewNamespaceStore(w.opts.Store, filepath.Join("workflows", eid))
options := NewExecuteOptions(opts...)
steps, err := w.getSteps(options.Start, options.Reverse)
if err != nil {
if werr := workflowStore.Write(w.opts.Context, "status", &codec.Frame{Data: []byte(StatusPending.String())}); werr != nil {
w.opts.Logger.Errorf(w.opts.Context, "store error: %v", werr)
}
return "", err return "", err
} }
var wg sync.WaitGroup var wg sync.WaitGroup
cherr := make(chan error, 1) cherr := make(chan error, 1)
defer close(cherr) chstatus := make(chan Status, 1)
nctx, cancel := context.WithCancel(ctx) nctx, cancel := context.WithCancel(ctx)
defer cancel() defer cancel()
nopts := make([]ExecuteOption, 0, len(opts)+5) nopts := make([]ExecuteOption, 0, len(opts)+5)
nopts = append(nopts, ExecuteClient(w.opts.Client), ExecuteTracer(w.opts.Tracer), ExecuteLogger(w.opts.Logger), ExecuteMeter(w.opts.Meter), ExecuteStore(w.opts.Store))
nopts = append(nopts,
ExecuteClient(w.opts.Client),
ExecuteTracer(w.opts.Tracer),
ExecuteLogger(w.opts.Logger),
ExecuteMeter(w.opts.Meter),
)
nopts = append(nopts, opts...)
done := make(chan struct{})
if werr := workflowStore.Write(w.opts.Context, "status", &codec.Frame{Data: []byte(StatusRunning.String())}); werr != nil {
w.opts.Logger.Errorf(w.opts.Context, "store error: %v", werr)
return eid, werr
}
for idx := range steps {
for nidx := range steps[idx] {
cstep := steps[idx][nidx]
if werr := stepStore.Write(ctx, filepath.Join(cstep.ID(), "status"), &codec.Frame{Data: []byte(StatusPending.String())}); werr != nil {
return eid, werr
}
}
}
go func() { go func() {
for idx := range steps { for idx := range steps {
wg.Add(len(steps[idx]))
for nidx := range steps[idx] { for nidx := range steps[idx] {
go func(step Step) { wStatus := &codec.Frame{}
defer wg.Done() if werr := workflowStore.Read(w.opts.Context, "status", wStatus); werr != nil {
if err = step.Execute(nctx, req, nopts...); err != nil { cherr <- werr
cherr <- err return
cancel() }
if status := StringStatus[string(wStatus.Data)]; status != StatusRunning {
chstatus <- status
return
}
if w.opts.Logger.V(logger.TraceLevel) {
w.opts.Logger.Tracef(nctx, "will be executed %v", steps[idx][nidx])
}
cstep := steps[idx][nidx]
// nolint: nestif
if len(cstep.Requires()) == 0 {
wg.Add(1)
go func(step Step) {
defer wg.Done()
if werr := stepStore.Write(ctx, filepath.Join(step.ID(), "req"), req); werr != nil {
cherr <- werr
return
}
if werr := stepStore.Write(ctx, filepath.Join(step.ID(), "status"), &codec.Frame{Data: []byte(StatusRunning.String())}); werr != nil {
cherr <- werr
return
}
rsp, serr := step.Execute(nctx, req, nopts...)
if serr != nil {
step.SetStatus(StatusFailure)
if werr := stepStore.Write(ctx, filepath.Join(step.ID(), "rsp"), serr); werr != nil && w.opts.Logger.V(logger.ErrorLevel) {
w.opts.Logger.Errorf(ctx, "store write error: %v", werr)
}
if werr := stepStore.Write(ctx, filepath.Join(step.ID(), "status"), &codec.Frame{Data: []byte(StatusFailure.String())}); werr != nil && w.opts.Logger.V(logger.ErrorLevel) {
w.opts.Logger.Errorf(ctx, "store write error: %v", werr)
}
cherr <- serr
return
}
if werr := stepStore.Write(ctx, filepath.Join(step.ID(), "rsp"), rsp); werr != nil {
w.opts.Logger.Errorf(ctx, "store write error: %v", werr)
cherr <- werr
return
}
if werr := stepStore.Write(ctx, filepath.Join(step.ID(), "status"), &codec.Frame{Data: []byte(StatusSuccess.String())}); werr != nil {
w.opts.Logger.Errorf(ctx, "store write error: %v", werr)
cherr <- werr
return
}
}(cstep)
wg.Wait()
} else {
if werr := stepStore.Write(ctx, filepath.Join(cstep.ID(), "req"), req); werr != nil {
cherr <- werr
return
} }
}(steps[idx][nidx]) if werr := stepStore.Write(ctx, filepath.Join(cstep.ID(), "status"), &codec.Frame{Data: []byte(StatusRunning.String())}); werr != nil {
cherr <- werr
return
}
rsp, serr := cstep.Execute(nctx, req, nopts...)
if serr != nil {
cstep.SetStatus(StatusFailure)
if werr := stepStore.Write(ctx, filepath.Join(cstep.ID(), "rsp"), serr); werr != nil && w.opts.Logger.V(logger.ErrorLevel) {
w.opts.Logger.Errorf(ctx, "store write error: %v", werr)
}
if werr := stepStore.Write(ctx, filepath.Join(cstep.ID(), "status"), &codec.Frame{Data: []byte(StatusFailure.String())}); werr != nil && w.opts.Logger.V(logger.ErrorLevel) {
w.opts.Logger.Errorf(ctx, "store write error: %v", werr)
}
cherr <- serr
return
}
if werr := stepStore.Write(ctx, filepath.Join(cstep.ID(), "rsp"), rsp); werr != nil {
w.opts.Logger.Errorf(ctx, "store write error: %v", werr)
cherr <- werr
return
}
if werr := stepStore.Write(ctx, filepath.Join(cstep.ID(), "status"), &codec.Frame{Data: []byte(StatusSuccess.String())}); werr != nil {
cherr <- werr
return
}
}
} }
wg.Wait()
} }
cherr <- nil close(done)
}() }()
err = <-cherr if options.Async {
return eid, nil
}
return uid.String(), err logger.Tracef(ctx, "wait for finish or error")
select {
case <-nctx.Done():
err = nctx.Err()
case cerr := <-cherr:
err = cerr
case <-done:
close(cherr)
case <-chstatus:
close(chstatus)
return eid, nil
}
switch {
case nctx.Err() != nil:
if werr := workflowStore.Write(w.opts.Context, "status", &codec.Frame{Data: []byte(StatusAborted.String())}); werr != nil {
w.opts.Logger.Errorf(w.opts.Context, "store error: %v", werr)
}
case err == nil:
if werr := workflowStore.Write(w.opts.Context, "status", &codec.Frame{Data: []byte(StatusSuccess.String())}); werr != nil {
w.opts.Logger.Errorf(w.opts.Context, "store error: %v", werr)
}
case err != nil:
if werr := workflowStore.Write(w.opts.Context, "status", &codec.Frame{Data: []byte(StatusFailure.String())}); werr != nil {
w.opts.Logger.Errorf(w.opts.Context, "store error: %v", werr)
}
}
return eid, err
} }
func NewFlow(opts ...Option) Flow { func NewFlow(opts ...Option) Flow {
@@ -204,9 +425,20 @@ func (f *microFlow) WorkflowLoad(ctx context.Context, id string) (Workflow, erro
} }
type microCallStep struct { type microCallStep struct {
opts StepOptions rsp *Message
req *Message
service string service string
method string method string
opts StepOptions
status Status
}
func (s *microCallStep) Request() *Message {
return s.req
}
func (s *microCallStep) Response() *Message {
return s.rsp
} }
func (s *microCallStep) ID() string { func (s *microCallStep) ID() string {
@@ -247,23 +479,49 @@ func (s *microCallStep) Hashcode() interface{} {
return s.String() return s.String()
} }
func (s *microCallStep) Execute(ctx context.Context, req interface{}, opts ...ExecuteOption) error { func (s *microCallStep) GetStatus() Status {
return s.status
}
func (s *microCallStep) SetStatus(status Status) {
s.status = status
}
func (s *microCallStep) Execute(ctx context.Context, req *Message, opts ...ExecuteOption) (*Message, error) {
options := NewExecuteOptions(opts...) options := NewExecuteOptions(opts...)
if options.Client == nil { if options.Client == nil {
return fmt.Errorf("client not set") return nil, ErrMissingClient
} }
rsp := &codec.Frame{} rsp := &codec.Frame{}
copts := []client.CallOption{client.WithRetries(0)} copts := []client.CallOption{client.WithRetries(0)}
if options.Timeout > 0 { if options.Timeout > 0 {
copts = append(copts, client.WithRequestTimeout(options.Timeout), client.WithDialTimeout(options.Timeout)) copts = append(copts,
client.WithRequestTimeout(options.Timeout),
client.WithDialTimeout(options.Timeout))
} }
err := options.Client.Call(ctx, options.Client.NewRequest(s.service, s.method, req), rsp) nctx := metadata.NewOutgoingContext(ctx, req.Header)
return err err := options.Client.Call(nctx, options.Client.NewRequest(s.service, s.method, &codec.Frame{Data: req.Body}), rsp, copts...)
if err != nil {
return nil, err
}
md, _ := metadata.FromOutgoingContext(nctx)
return &Message{Header: md, Body: rsp.Data}, err
} }
type microPublishStep struct { type microPublishStep struct {
opts StepOptions req *Message
topic string rsp *Message
topic string
opts StepOptions
status Status
}
func (s *microPublishStep) Request() *Message {
return s.req
}
func (s *microPublishStep) Response() *Message {
return s.rsp
} }
func (s *microPublishStep) ID() string { func (s *microPublishStep) ID() string {
@@ -293,7 +551,7 @@ func (s *microPublishStep) String() string {
if s.opts.ID != "" { if s.opts.ID != "" {
return s.opts.ID return s.opts.ID
} }
return fmt.Sprintf("%s", s.topic) return s.topic
} }
func (s *microPublishStep) Name() string { func (s *microPublishStep) Name() string {
@@ -304,13 +562,21 @@ func (s *microPublishStep) Hashcode() interface{} {
return s.String() return s.String()
} }
func (s *microPublishStep) Execute(ctx context.Context, req interface{}, opts ...ExecuteOption) error { func (s *microPublishStep) GetStatus() Status {
return nil return s.status
} }
func NewCallStep(service string, method string, opts ...StepOption) Step { func (s *microPublishStep) SetStatus(status Status) {
s.status = status
}
func (s *microPublishStep) Execute(ctx context.Context, req *Message, opts ...ExecuteOption) (*Message, error) {
return nil, nil
}
func NewCallStep(service string, name string, method string, opts ...StepOption) Step {
options := NewStepOptions(opts...) options := NewStepOptions(opts...)
return &microCallStep{service: service, method: method, opts: options} return &microCallStep{service: service, method: name + "." + method, opts: options}
} }
func NewPublishStep(topic string, opts ...StepOption) Step { func NewPublishStep(topic string, opts ...StepOption) Step {

View File

@@ -1,15 +1,46 @@
// Package flow is an interface used for saga pattern microservice workflow // Package flow is an interface used for saga pattern microservice workflow
package flow package flow // import "go.unistack.org/micro/v3/flow"
import ( import (
"context" "context"
"errors" "errors"
"sync"
"sync/atomic"
"go.unistack.org/micro/v3/metadata"
) )
var ( var (
ErrStepNotExists = errors.New("step not exists") ErrStepNotExists = errors.New("step not exists")
ErrMissingClient = errors.New("client not set")
) )
// RawMessage is a raw encoded JSON value.
// It implements Marshaler and Unmarshaler and can be used to delay decoding or precompute a encoding.
type RawMessage []byte
// MarshalJSON returns m as the JSON encoding of m.
func (m *RawMessage) MarshalJSON() ([]byte, error) {
if m == nil {
return []byte("null"), nil
}
return *m, nil
}
// UnmarshalJSON sets *m to a copy of data.
func (m *RawMessage) UnmarshalJSON(data []byte) error {
if m == nil {
return errors.New("RawMessage UnmarshalJSON on nil pointer")
}
*m = append((*m)[0:0], data...)
return nil
}
type Message struct {
Header metadata.Metadata
Body RawMessage
}
// Step represents dedicated workflow step // Step represents dedicated workflow step
type Step interface { type Step interface {
// ID returns step id // ID returns step id
@@ -17,7 +48,7 @@ type Step interface {
// Endpoint returns rpc endpoint service_name.service_method or broker topic // Endpoint returns rpc endpoint service_name.service_method or broker topic
Endpoint() string Endpoint() string
// Execute step run // Execute step run
Execute(ctx context.Context, req interface{}, opts ...ExecuteOption) error Execute(ctx context.Context, req *Message, opts ...ExecuteOption) (*Message, error)
// Requires returns dependent steps // Requires returns dependent steps
Requires() []string Requires() []string
// Options returns step options // Options returns step options
@@ -26,20 +57,70 @@ type Step interface {
Require(steps ...Step) error Require(steps ...Step) error
// String // String
String() string String() string
// GetStatus returns step status
GetStatus() Status
// SetStatus sets the step status
SetStatus(Status)
// Request returns step request message
Request() *Message
// Response returns step response message
Response() *Message
} }
type Status int
func (status Status) String() string {
return StatusString[status]
}
const (
StatusPending Status = iota
StatusRunning
StatusFailure
StatusSuccess
StatusAborted
StatusSuspend
)
var (
StatusString = map[Status]string{
StatusPending: "StatusPending",
StatusRunning: "StatusRunning",
StatusFailure: "StatusFailure",
StatusSuccess: "StatusSuccess",
StatusAborted: "StatusAborted",
StatusSuspend: "StatusSuspend",
}
StringStatus = map[string]Status{
"StatusPending": StatusPending,
"StatusRunning": StatusRunning,
"StatusFailure": StatusFailure,
"StatusSuccess": StatusSuccess,
"StatusAborted": StatusAborted,
"StatusSuspend": StatusSuspend,
}
)
// Workflow contains all steps to execute // Workflow contains all steps to execute
type Workflow interface { type Workflow interface {
// ID returns id of the workflow // ID returns id of the workflow
ID() string ID() string
// Steps returns steps slice where parallel steps returned on the same level
Steps() [][]Step
// Execute workflow with args, return execution id and error // Execute workflow with args, return execution id and error
Execute(ctx context.Context, req interface{}, opts ...ExecuteOption) (string, error) Execute(ctx context.Context, req *Message, opts ...ExecuteOption) (string, error)
// RemoveSteps remove steps from workflow // RemoveSteps remove steps from workflow
RemoveSteps(ctx context.Context, steps ...Step) error RemoveSteps(steps ...Step) error
// AppendSteps append steps to workflow // AppendSteps append steps to workflow
AppendSteps(ctx context.Context, steps ...Step) error AppendSteps(steps ...Step) error
// Status returns workflow status
Status() Status
// Steps returns steps slice where parallel steps returned on the same level
Steps() ([][]Step, error)
// Suspend suspends execution
Suspend(ctx context.Context, id string) error
// Resume resumes execution
Resume(ctx context.Context, id string) error
// Abort abort execution
Abort(ctx context.Context, id string) error
} }
// Flow the base interface to interact with workflows // Flow the base interface to interact with workflows
@@ -57,3 +138,15 @@ type Flow interface {
// WorkflowList lists all workflows // WorkflowList lists all workflows
WorkflowList(ctx context.Context) ([]Workflow, error) WorkflowList(ctx context.Context) ([]Workflow, error)
} }
var (
flowMu sync.Mutex
atomicSteps atomic.Value
)
func RegisterStep(step Step) {
flowMu.Lock()
steps, _ := atomicSteps.Load().([]Step)
atomicSteps.Store(append(steps, step))
flowMu.Unlock()
}

View File

@@ -4,11 +4,11 @@ import (
"context" "context"
"time" "time"
"github.com/unistack-org/micro/v3/client" "go.unistack.org/micro/v3/client"
"github.com/unistack-org/micro/v3/logger" "go.unistack.org/micro/v3/logger"
"github.com/unistack-org/micro/v3/meter" "go.unistack.org/micro/v3/meter"
"github.com/unistack-org/micro/v3/store" "go.unistack.org/micro/v3/store"
"github.com/unistack-org/micro/v3/tracer" "go.unistack.org/micro/v3/tracer"
) )
// Option func // Option func
@@ -96,8 +96,8 @@ type WorkflowOption func(*WorkflowOptions)
// WorkflowOptions holds workflow options // WorkflowOptions holds workflow options
type WorkflowOptions struct { type WorkflowOptions struct {
ID string
Context context.Context Context context.Context
ID string
} }
// WorkflowID set workflow id // WorkflowID set workflow id
@@ -116,16 +116,16 @@ type ExecuteOptions struct {
Logger logger.Logger Logger logger.Logger
// Meter holds the meter // Meter holds the meter
Meter meter.Meter Meter meter.Meter
// Store used for intermediate results
Store store.Store
// Context can be used to abort execution or pass additional opts // Context can be used to abort execution or pass additional opts
Context context.Context Context context.Context
// Start step // Start step
Start string Start string
// Reverse execution
Reverse bool
// Timeout for execution // Timeout for execution
Timeout time.Duration Timeout time.Duration
// Reverse execution
Reverse bool
// Async enables async execution
Async bool
} }
type ExecuteOption func(*ExecuteOptions) type ExecuteOption func(*ExecuteOptions)
@@ -154,12 +154,6 @@ func ExecuteMeter(m meter.Meter) ExecuteOption {
} }
} }
func ExecuteStore(s store.Store) ExecuteOption {
return func(o *ExecuteOptions) {
o.Store = s
}
}
func ExecuteContext(ctx context.Context) ExecuteOption { func ExecuteContext(ctx context.Context) ExecuteOption {
return func(o *ExecuteOptions) { return func(o *ExecuteOptions) {
o.Context = ctx o.Context = ctx
@@ -178,8 +172,20 @@ func ExecuteTimeout(td time.Duration) ExecuteOption {
} }
} }
func ExecuteAsync(b bool) ExecuteOption {
return func(o *ExecuteOptions) {
o.Async = b
}
}
func NewExecuteOptions(opts ...ExecuteOption) ExecuteOptions { func NewExecuteOptions(opts ...ExecuteOption) ExecuteOptions {
options := ExecuteOptions{} options := ExecuteOptions{
Client: client.DefaultClient,
Logger: logger.DefaultLogger,
Tracer: tracer.DefaultTracer,
Meter: meter.DefaultMeter,
Context: context.Background(),
}
for _, o := range opts { for _, o := range opts {
o(&options) o(&options)
} }
@@ -187,16 +193,18 @@ func NewExecuteOptions(opts ...ExecuteOption) ExecuteOptions {
} }
type StepOptions struct { type StepOptions struct {
ID string
Context context.Context Context context.Context
Requires []string
Fallback string Fallback string
ID string
Requires []string
} }
type StepOption func(*StepOptions) type StepOption func(*StepOptions)
func NewStepOptions(opts ...StepOption) StepOptions { func NewStepOptions(opts ...StepOption) StepOptions {
options := StepOptions{Context: context.Background()} options := StepOptions{
Context: context.Background(),
}
for _, o := range opts { for _, o := range opts {
o(&options) o(&options)
} }

View File

@@ -6,7 +6,7 @@ import (
"context" "context"
"time" "time"
"github.com/unistack-org/micro/v3/server" "go.unistack.org/micro/v3/server"
) )
// Function is a one time executing Service // Function is a one time executing Service

View File

@@ -8,7 +8,7 @@ import (
"sync" "sync"
"testing" "testing"
"github.com/unistack-org/micro/v3/register" "go.unistack.org/micro/v3/register"
) )
func TestFunction(t *testing.T) { func TestFunction(t *testing.T) {

11
go.mod
View File

@@ -1,13 +1,14 @@
module github.com/unistack-org/micro/v3 module go.unistack.org/micro/v3
go 1.16 go 1.16
require ( require (
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/ef-ds/deque v1.0.4 github.com/ef-ds/deque v1.0.4
github.com/google/uuid v1.2.0 github.com/golang-jwt/jwt/v4 v4.1.0
github.com/imdario/mergo v0.3.12 github.com/imdario/mergo v0.3.12
github.com/patrickmn/go-cache v2.1.0+incompatible github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/silas/dag v0.0.0-20210121180416-41cf55125c34 github.com/silas/dag v0.0.0-20210626123444-3804bac2d6d4
golang.org/x/net v0.0.0-20210510120150-4163338589ed go.unistack.org/micro-proto/v3 v3.1.0
golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
) )

25
go.sum
View File

@@ -1,22 +1,31 @@
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/ef-ds/deque v1.0.4 h1:iFAZNmveMT9WERAkqLJ+oaABF9AcVQ5AjXem/hroniI= github.com/ef-ds/deque v1.0.4 h1:iFAZNmveMT9WERAkqLJ+oaABF9AcVQ5AjXem/hroniI=
github.com/ef-ds/deque v1.0.4/go.mod h1:gXDnTC3yqvBcHbq2lcExjtAcVrOnJCbMcZXmuj8Z4tg= github.com/ef-ds/deque v1.0.4/go.mod h1:gXDnTC3yqvBcHbq2lcExjtAcVrOnJCbMcZXmuj8Z4tg=
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= github.com/golang-jwt/jwt/v4 v4.1.0 h1:XUgk2Ex5veyVFVeLm0xhusUTQybEbexJXrvPNOKkSY0=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/golang-jwt/jwt/v4 v4.1.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/silas/dag v0.0.0-20210121180416-41cf55125c34 h1:vBfVmA5mZhsQa2jr1FOL9nfA37N/jnbBmi5XUfviVTI= github.com/silas/dag v0.0.0-20210626123444-3804bac2d6d4 h1:fOH64AB0C3ixGf9emky61STvPJL3smxJg+1Zwx1oCdg=
github.com/silas/dag v0.0.0-20210121180416-41cf55125c34/go.mod h1:7RTUFBdIRC9nZ7/3RyRNH1bdqIShrDejd1YbLwgPS+I= github.com/silas/dag v0.0.0-20210626123444-3804bac2d6d4/go.mod h1:7RTUFBdIRC9nZ7/3RyRNH1bdqIShrDejd1YbLwgPS+I=
golang.org/x/net v0.0.0-20210510120150-4163338589ed h1:p9UgmWI9wKpfYmgaV/IZKGdXc5qEK45tDwwwDyjS26I= go.unistack.org/micro-proto/v3 v3.1.0 h1:q39FwjFiRZn+Ux/tt+d3bJTmDtsQQWa+3SLYVo1vLfA=
golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= go.unistack.org/micro-proto/v3 v3.1.0/go.mod h1:DpRhYCBXlmSJ/AAXTmntvlh7kQkYU6eFvlmYAx4BQS8=
golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b h1:eB48h3HiRycXNy8E0Gf5e0hv7YT6Kt14L/D73G1fuwo=
golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@@ -11,21 +11,12 @@ import (
"time" "time"
) )
func init() {
lvl, err := GetLevel(os.Getenv("MICRO_LOG_LEVEL"))
if err != nil {
lvl = InfoLevel
}
DefaultLogger = NewLogger(WithLevel(lvl))
}
type defaultLogger struct { type defaultLogger struct {
enc *json.Encoder enc *json.Encoder
opts Options
sync.RWMutex
logFunc LogFunc logFunc LogFunc
logfFunc LogfFunc logfFunc LogfFunc
opts Options
sync.RWMutex
} }
// Init(opts...) should only overwrite provided options // Init(opts...) should only overwrite provided options
@@ -35,15 +26,11 @@ func (l *defaultLogger) Init(opts ...Option) error {
o(&l.opts) o(&l.opts)
} }
l.enc = json.NewEncoder(l.opts.Out) l.enc = json.NewEncoder(l.opts.Out)
l.logFunc = l.Log
l.logfFunc = l.Logf
// wrap the Log func // wrap the Log func
for i := len(l.opts.Wrappers); i > 0; i-- { for i := len(l.opts.Wrappers); i > 0; i-- {
l.logFunc = l.opts.Wrappers[i-1].Log(l.logFunc) l.logFunc = l.opts.Wrappers[i-1].Log(l.logFunc)
l.logfFunc = l.opts.Wrappers[i-1].Logf(l.logfFunc) l.logfFunc = l.opts.Wrappers[i-1].Logf(l.logfFunc)
} }
l.Unlock() l.Unlock()
return nil return nil
} }
@@ -52,6 +39,28 @@ func (l *defaultLogger) String() string {
return "micro" return "micro"
} }
func (l *defaultLogger) Clone(opts ...Option) Logger {
newopts := NewOptions(opts...)
oldopts := l.opts
for _, o := range opts {
o(&newopts)
o(&oldopts)
}
oldopts.Wrappers = newopts.Wrappers
l.Lock()
cl := &defaultLogger{opts: oldopts, logFunc: l.logFunc, logfFunc: l.logfFunc}
l.Unlock()
// wrap the Log func
for i := len(newopts.Wrappers); i > 0; i-- {
cl.logFunc = newopts.Wrappers[i-1].Log(cl.logFunc)
cl.logfFunc = newopts.Wrappers[i-1].Logf(cl.logfFunc)
}
return cl
}
func (l *defaultLogger) V(level Level) bool { func (l *defaultLogger) V(level Level) bool {
l.RLock() l.RLock()
ok := l.opts.Level.Enabled(level) ok := l.opts.Level.Enabled(level)
@@ -59,26 +68,26 @@ func (l *defaultLogger) V(level Level) bool {
return ok return ok
} }
func (l *defaultLogger) Fields(fields map[string]interface{}) Logger { func (l *defaultLogger) Level(level Level) {
nl := &defaultLogger{opts: l.opts, enc: l.enc} l.Lock()
nl.opts.Fields = make(map[string]interface{}, len(l.opts.Fields)+len(fields)) l.opts.Level = level
l.RLock() l.Unlock()
for k, v := range l.opts.Fields { }
nl.opts.Fields[k] = v
}
l.RUnlock()
for k, v := range fields { func (l *defaultLogger) Fields(fields ...interface{}) Logger {
nl.opts.Fields[k] = v nl := &defaultLogger{opts: l.opts, enc: l.enc}
if len(fields) == 0 {
return nl
} else if len(fields)%2 != 0 {
fields = fields[:len(fields)-1]
} }
nl.opts.Fields = append(nl.opts.Fields, fields...)
return nl return nl
} }
func copyFields(src map[string]interface{}) map[string]interface{} { func copyFields(src []interface{}) []interface{} {
dst := make(map[string]interface{}, len(src)) dst := make([]interface{}, len(src))
for k, v := range src { copy(dst, src)
dst[k] = v
}
return dst return dst
} }
@@ -165,19 +174,23 @@ func (l *defaultLogger) Log(ctx context.Context, level Level, args ...interface{
fields := copyFields(l.opts.Fields) fields := copyFields(l.opts.Fields)
l.RUnlock() l.RUnlock()
fields["level"] = level.String() fields = append(fields, "level", level.String())
if _, file, line, ok := runtime.Caller(l.opts.CallerSkipCount); ok { if _, file, line, ok := runtime.Caller(l.opts.CallerSkipCount); ok {
fields["caller"] = fmt.Sprintf("%s:%d", logCallerfilePath(file), line) fields = append(fields, "caller", fmt.Sprintf("%s:%d", logCallerfilePath(file), line))
} }
fields = append(fields, "timestamp", time.Now().Format("2006-01-02 15:04:05"))
fields["timestamp"] = time.Now().Format("2006-01-02 15:04:05")
if len(args) > 0 { if len(args) > 0 {
fields["msg"] = fmt.Sprint(args...) fields = append(fields, "msg", fmt.Sprint(args...))
} }
out := make(map[string]interface{}, len(fields)/2)
for i := 0; i < len(fields); i += 2 {
out[fields[i].(string)] = fields[i+1]
}
l.RLock() l.RLock()
_ = l.enc.Encode(fields) _ = l.enc.Encode(out)
l.RUnlock() l.RUnlock()
} }
@@ -190,35 +203,39 @@ func (l *defaultLogger) Logf(ctx context.Context, level Level, msg string, args
fields := copyFields(l.opts.Fields) fields := copyFields(l.opts.Fields)
l.RUnlock() l.RUnlock()
fields["level"] = level.String() fields = append(fields, "level", level.String())
if _, file, line, ok := runtime.Caller(l.opts.CallerSkipCount); ok { if _, file, line, ok := runtime.Caller(l.opts.CallerSkipCount); ok {
fields["caller"] = fmt.Sprintf("%s:%d", logCallerfilePath(file), line) fields = append(fields, "caller", fmt.Sprintf("%s:%d", logCallerfilePath(file), line))
} }
fields["timestamp"] = time.Now().Format("2006-01-02 15:04:05") fields = append(fields, "timestamp", time.Now().Format("2006-01-02 15:04:05"))
if len(args) > 0 { if len(args) > 0 {
fields["msg"] = fmt.Sprintf(msg, args...) fields = append(fields, "msg", fmt.Sprintf(msg, args...))
} else if msg != "" { } else if msg != "" {
fields["msg"] = msg fields = append(fields, "msg", msg)
}
out := make(map[string]interface{}, len(fields)/2)
for i := 0; i < len(fields); i += 2 {
out[fields[i].(string)] = fields[i+1]
} }
l.RLock() l.RLock()
_ = l.enc.Encode(fields) _ = l.enc.Encode(out)
l.RUnlock() l.RUnlock()
} }
func (l *defaultLogger) Options() Options { func (l *defaultLogger) Options() Options {
// not guard against options Context values return l.opts
l.RLock()
opts := l.opts
opts.Fields = copyFields(l.opts.Fields)
l.RUnlock()
return opts
} }
// NewLogger builds a new logger based on options // NewLogger builds a new logger based on options
func NewLogger(opts ...Option) Logger { func NewLogger(opts ...Option) Logger {
l := &defaultLogger{opts: NewOptions(opts...)} l := &defaultLogger{
opts: NewOptions(opts...),
}
l.logFunc = l.Log
l.logfFunc = l.Logf
l.enc = json.NewEncoder(l.opts.Out) l.enc = json.NewEncoder(l.opts.Out)
return l return l
} }

View File

@@ -1,24 +1,20 @@
package logger package logger
import (
"fmt"
)
// Level means logger level // Level means logger level
type Level int8 type Level int8
const ( const (
// TraceLevel level. Designates finer-grained informational events than the Debug. // TraceLevel level usually used to find bugs, very verbose
TraceLevel Level = iota - 2 TraceLevel Level = iota - 2
// DebugLevel level. Usually only enabled when debugging. Very verbose logging. // DebugLevel level used only when enabled debugging
DebugLevel DebugLevel
// InfoLevel level. General operational entries about what's going on inside the application. // InfoLevel level used for general info about what's going on inside the application
InfoLevel InfoLevel
// WarnLevel level. Non-critical entries that deserve eyes. // WarnLevel level used for non-critical entries
WarnLevel WarnLevel
// ErrorLevel level. Used for errors that should definitely be noted. // ErrorLevel level used for errors that should definitely be noted
ErrorLevel ErrorLevel
// FatalLevel level. Logs and then calls `os.Exit(1)`. highest level of severity. // FatalLevel level used for critical errors and then calls `os.Exit(1)`
FatalLevel FatalLevel
) )
@@ -38,7 +34,7 @@ func (l Level) String() string {
case FatalLevel: case FatalLevel:
return "fatal" return "fatal"
} }
return "" return "info"
} }
// Enabled returns true if the given level is at or above this level. // Enabled returns true if the given level is at or above this level.
@@ -46,22 +42,22 @@ func (l Level) Enabled(lvl Level) bool {
return lvl >= l return lvl >= l
} }
// GetLevel converts a level string into a logger Level value. // ParseLevel converts a level string into a logger Level value.
// returns an error if the input string does not match known values. // returns an InfoLevel if the input string does not match known values.
func GetLevel(levelStr string) (Level, error) { func ParseLevel(lvl string) Level {
switch levelStr { switch lvl {
case TraceLevel.String(): case TraceLevel.String():
return TraceLevel, nil return TraceLevel
case DebugLevel.String(): case DebugLevel.String():
return DebugLevel, nil return DebugLevel
case InfoLevel.String(): case InfoLevel.String():
return InfoLevel, nil return InfoLevel
case WarnLevel.String(): case WarnLevel.String():
return WarnLevel, nil return WarnLevel
case ErrorLevel.String(): case ErrorLevel.String():
return ErrorLevel, nil return ErrorLevel
case FatalLevel.String(): case FatalLevel.String():
return FatalLevel, nil return FatalLevel
} }
return InfoLevel, fmt.Errorf("unknown Level String: '%s', use InfoLevel", levelStr) return InfoLevel
} }

View File

@@ -1,25 +1,34 @@
// Package logger provides a log interface // Package logger provides a log interface
package logger package logger // import "go.unistack.org/micro/v3/logger"
import "context" import (
"context"
"os"
)
var ( var (
// DefaultLogger variable // DefaultLogger variable
DefaultLogger Logger = NewLogger() DefaultLogger Logger = NewLogger(WithLevel(ParseLevel(os.Getenv("MICRO_LOG_LEVEL"))))
// DefaultLevel used by logger // DefaultLevel used by logger
DefaultLevel Level = InfoLevel DefaultLevel Level = InfoLevel
// DefaultCallerSkipCount used by logger
DefaultCallerSkipCount = 2
) )
// Logger is a generic logging interface // Logger is a generic logging interface
type Logger interface { type Logger interface {
// Init initialises options // Init initialises options
Init(opts ...Option) error Init(opts ...Option) error
// Clone create logger copy with new options
Clone(opts ...Option) Logger
// V compare provided verbosity level with current log level // V compare provided verbosity level with current log level
V(level Level) bool V(level Level) bool
// Level sets the log level for logger
Level(level Level)
// The Logger options // The Logger options
Options() Options Options() Options
// Fields set fields to always be logged // Fields set fields to always be logged with keyval pairs
Fields(fields map[string]interface{}) Logger Fields(fields ...interface{}) Logger
// Info level message // Info level message
Info(ctx context.Context, args ...interface{}) Info(ctx context.Context, args ...interface{})
// Trace level message // Trace level message
@@ -52,6 +61,9 @@ type Logger interface {
String() string String() string
} }
// Field contains keyval pair
type Field interface{}
// Info writes msg to default logger on info level // Info writes msg to default logger on info level
func Info(ctx context.Context, args ...interface{}) { func Info(ctx context.Context, args ...interface{}) {
DefaultLogger.Info(ctx, args...) DefaultLogger.Info(ctx, args...)
@@ -123,6 +135,6 @@ func Init(opts ...Option) error {
} }
// Fields create logger with specific fields // Fields create logger with specific fields
func Fields(fields map[string]interface{}) Logger { func Fields(fields ...interface{}) Logger {
return DefaultLogger.Fields(fields) return DefaultLogger.Fields(fields...)
} }

View File

@@ -3,9 +3,58 @@ package logger
import ( import (
"bytes" "bytes"
"context" "context"
"log"
"testing" "testing"
) )
func TestClone(t *testing.T) {
ctx := context.TODO()
buf := bytes.NewBuffer(nil)
l := NewLogger(WithLevel(TraceLevel), WithOutput(buf))
if err := l.Init(); err != nil {
t.Fatal(err)
}
nl := l.Clone(WithLevel(ErrorLevel))
if err := nl.Init(); err != nil {
t.Fatal(err)
}
nl.Info(ctx, "info message")
if len(buf.Bytes()) != 0 {
t.Fatal("message must not be logged")
}
l.Info(ctx, "info message")
if len(buf.Bytes()) == 0 {
t.Fatal("message must be logged")
}
}
func TestRedirectStdLogger(t *testing.T) {
buf := bytes.NewBuffer(nil)
l := NewLogger(WithLevel(TraceLevel), WithOutput(buf))
if err := l.Init(); err != nil {
t.Fatal(err)
}
fn := RedirectStdLogger(l, ErrorLevel)
defer fn()
log.Print("test")
if !bytes.Contains(buf.Bytes(), []byte(`"level":"error","msg":"test","timestamp"`)) {
t.Fatalf("logger error, buf %s", buf.Bytes())
}
}
func TestStdLogger(t *testing.T) {
buf := bytes.NewBuffer(nil)
l := NewLogger(WithLevel(TraceLevel), WithOutput(buf))
if err := l.Init(); err != nil {
t.Fatal(err)
}
lg := NewStdLogger(l, ErrorLevel)
lg.Print("test")
if !bytes.Contains(buf.Bytes(), []byte(`"level":"error","msg":"test","timestamp"`)) {
t.Fatalf("logger error, buf %s", buf.Bytes())
}
}
func TestLogger(t *testing.T) { func TestLogger(t *testing.T) {
ctx := context.TODO() ctx := context.TODO()
buf := bytes.NewBuffer(nil) buf := bytes.NewBuffer(nil)
@@ -15,7 +64,7 @@ func TestLogger(t *testing.T) {
} }
l.Trace(ctx, "trace_msg1") l.Trace(ctx, "trace_msg1")
l.Warn(ctx, "warn_msg1") l.Warn(ctx, "warn_msg1")
l.Fields(map[string]interface{}{"error": "test"}).Info(ctx, "error message") l.Fields("error", "test").Info(ctx, "error message")
l.Warn(ctx, "first", " ", "second") l.Warn(ctx, "first", " ", "second")
if !bytes.Contains(buf.Bytes(), []byte(`"level":"trace","msg":"trace_msg1"`)) { if !bytes.Contains(buf.Bytes(), []byte(`"level":"trace","msg":"trace_msg1"`)) {
t.Fatalf("logger error, buf %s", buf.Bytes()) t.Fatalf("logger error, buf %s", buf.Bytes())

View File

@@ -16,24 +16,24 @@ type Options struct {
// Context holds exernal options // Context holds exernal options
Context context.Context Context context.Context
// Fields holds additional metadata // Fields holds additional metadata
Fields map[string]interface{} Fields []interface{}
// Name holds the logger name // Name holds the logger name
Name string Name string
// CallerSkipCount number of frmaes to skip
CallerSkipCount int
// The logging level the logger should log
Level Level
// Wrappers logger wrapper that called before actual Log/Logf function // Wrappers logger wrapper that called before actual Log/Logf function
Wrappers []Wrapper Wrappers []Wrapper
// The logging level the logger should log
Level Level
// CallerSkipCount number of frmaes to skip
CallerSkipCount int
} }
// NewOptions creates new options struct // NewOptions creates new options struct
func NewOptions(opts ...Option) Options { func NewOptions(opts ...Option) Options {
options := Options{ options := Options{
Level: DefaultLevel, Level: DefaultLevel,
Fields: make(map[string]interface{}), Fields: make([]interface{}, 0, 6),
Out: os.Stderr, Out: os.Stderr,
CallerSkipCount: 0, CallerSkipCount: DefaultCallerSkipCount,
Context: context.Background(), Context: context.Background(),
} }
for _, o := range opts { for _, o := range opts {
@@ -43,7 +43,7 @@ func NewOptions(opts ...Option) Options {
} }
// WithFields set default fields for the logger // WithFields set default fields for the logger
func WithFields(fields map[string]interface{}) Option { func WithFields(fields ...interface{}) Option {
return func(o *Options) { return func(o *Options) {
o.Fields = fields o.Fields = fields
} }

35
logger/stdlogger.go Normal file
View File

@@ -0,0 +1,35 @@
package logger
import (
"bytes"
"log"
)
type stdLogger struct {
l Logger
level Level
}
func NewStdLogger(l Logger, level Level) *log.Logger {
return log.New(&stdLogger{l: l, level: level}, "" /* prefix */, 0 /* flags */)
}
func (sl *stdLogger) Write(p []byte) (int, error) {
p = bytes.TrimSpace(p)
sl.l.Log(sl.l.Options().Context, sl.level, string(p))
return len(p), nil
}
func RedirectStdLogger(l Logger, level Level) func() {
flags := log.Flags()
prefix := log.Prefix()
writer := log.Writer()
log.SetFlags(0)
log.SetPrefix("")
log.SetOutput(&stdLogger{l: l, level: level})
return func() {
log.SetFlags(flags)
log.SetPrefix(prefix)
log.SetOutput(writer)
}
}

View File

@@ -1,10 +1,10 @@
package logger package logger // import "go.unistack.org/micro/v3/logger/wrapper"
import ( import (
"context" "context"
"reflect" "reflect"
rutil "github.com/unistack-org/micro/v3/util/reflect" rutil "go.unistack.org/micro/v3/util/reflect"
) )
// LogFunc function used for Log method // LogFunc function used for Log method
@@ -20,6 +20,8 @@ type Wrapper interface {
Logf(LogfFunc) LogfFunc Logf(LogfFunc) LogfFunc
} }
var _ Logger = &OmitLogger{}
type OmitLogger struct { type OmitLogger struct {
l Logger l Logger
} }
@@ -36,12 +38,20 @@ func (w *OmitLogger) V(level Level) bool {
return w.l.V(level) return w.l.V(level)
} }
func (w *OmitLogger) Level(level Level) {
w.l.Level(level)
}
func (w *OmitLogger) Clone(opts ...Option) Logger {
return w.l.Clone(opts...)
}
func (w *OmitLogger) Options() Options { func (w *OmitLogger) Options() Options {
return w.l.Options() return w.l.Options()
} }
func (w *OmitLogger) Fields(fields map[string]interface{}) Logger { func (w *OmitLogger) Fields(fields ...interface{}) Logger {
return w.l.Fields(fields) return w.l.Fields(fields...)
} }
func (w *OmitLogger) Info(ctx context.Context, args ...interface{}) { func (w *OmitLogger) Info(ctx context.Context, args ...interface{}) {
@@ -115,23 +125,29 @@ func getArgs(args []interface{}) []interface{} {
var err error var err error
for _, arg := range args { for _, arg := range args {
val := reflect.ValueOf(arg) val := reflect.ValueOf(arg)
switch val.Kind() { if val.Kind() == reflect.Ptr {
case reflect.Ptr:
val = val.Elem() val = val.Elem()
} }
narg := arg narg := arg
if val.Kind() == reflect.Struct { if val.Kind() != reflect.Struct {
if narg, err = rutil.Zero(arg); err == nil { nargs = append(nargs, narg)
rutil.CopyDefaults(narg, arg) continue
if flds, ferr := rutil.StructFields(narg); ferr == nil { }
for _, fld := range flds {
if tv, ok := fld.Field.Tag.Lookup("logger"); ok && tv == "omit" { if narg, err = rutil.Zero(arg); err != nil {
fld.Value.Set(reflect.Zero(fld.Value.Type())) nargs = append(nargs, narg)
} continue
} }
rutil.CopyDefaults(narg, arg)
if flds, ferr := rutil.StructFields(narg); ferr == nil {
for _, fld := range flds {
if tv, ok := fld.Field.Tag.Lookup("logger"); ok && tv == "omit" {
fld.Value.Set(reflect.Zero(fld.Value.Type()))
} }
} }
} }
nargs = append(nargs, narg) nargs = append(nargs, narg)
} }
return nargs return nargs

View File

@@ -5,9 +5,9 @@ import (
"context" "context"
"fmt" "fmt"
"github.com/unistack-org/micro/v3/client" "go.unistack.org/micro/v3/client"
"github.com/unistack-org/micro/v3/logger" "go.unistack.org/micro/v3/logger"
"github.com/unistack-org/micro/v3/server" "go.unistack.org/micro/v3/server"
) )
var ( var (

View File

@@ -1,13 +1,25 @@
// Package metadata is a way of defining message headers // Package metadata is a way of defining message headers
package metadata package metadata // import "go.unistack.org/micro/v3/metadata"
import ( import (
"net/textproto" "net/textproto"
"sort" "sort"
) )
// HeaderPrefix for all headers passed var (
var HeaderPrefix = "Micro-" // HeaderTopic is the header name that contains topic name
HeaderTopic = "Micro-Topic"
// HeaderContentType specifies content type of message
HeaderContentType = "Content-Type"
// HeaderEndpoint specifies endpoint in service
HeaderEndpoint = "Micro-Endpoint"
// HeaderService specifies service
HeaderService = "Micro-Service"
// HeaderTimeout specifies timeout of operation
HeaderTimeout = "Micro-Timeout"
// HeaderAuthorization specifies Authorization header
HeaderAuthorization = "Authorization"
)
// Metadata is our way of representing request headers internally. // Metadata is our way of representing request headers internally.
// They're used at the RPC level and translate back and forth // They're used at the RPC level and translate back and forth
@@ -95,13 +107,16 @@ func New(size int) Metadata {
// Merge merges metadata to existing metadata, overwriting if specified // Merge merges metadata to existing metadata, overwriting if specified
func Merge(omd Metadata, mmd Metadata, overwrite bool) Metadata { func Merge(omd Metadata, mmd Metadata, overwrite bool) Metadata {
var ok bool
nmd := Copy(omd) nmd := Copy(omd)
for key, val := range mmd { for key, val := range mmd {
if _, ok := nmd[key]; ok && !overwrite { _, ok = nmd[key]
// skip switch {
} else if val != "" { case ok && !overwrite:
continue
case val != "":
nmd.Set(key, val) nmd.Set(key, val)
} else { case ok && val == "":
nmd.Del(key) nmd.Del(key)
} }
} }

View File

@@ -1,3 +1,12 @@
package meter package meter
//go:generate protoc -I./handler -I../ -I/home/vtolstov/.cache/go-path/pkg/mod/github.com/unistack-org/micro-proto@v0.0.1 --micro_out=components=micro|http|server,standalone=false,debug=true,paths=source_relative:./handler handler/handler.proto //go:generate sh -c "protoc -I./handler -I../ -I$(go list -f '{{ .Dir }}' -m go.unistack.org/micro-proto/v3) --go-micro_out='components=micro|http|server',standalone=false,debug=true,paths=source_relative:./handler handler/handler.proto"
import (
// import required packages
_ "go.unistack.org/micro-proto/v3/api"
// import required packages
_ "go.unistack.org/micro-proto/v3/openapiv3"
)

View File

@@ -1,12 +1,12 @@
package handler package handler // import "go.unistack.org/micro/v3/meter/handler"
import ( import (
"bytes" "bytes"
"context" "context"
"github.com/unistack-org/micro/v3/codec" "go.unistack.org/micro/v3/codec"
"github.com/unistack-org/micro/v3/errors" "go.unistack.org/micro/v3/errors"
"github.com/unistack-org/micro/v3/meter" "go.unistack.org/micro/v3/meter"
) )
// guard to fail early // guard to fail early

View File

@@ -1,27 +1,23 @@
syntax = "proto3"; syntax = "proto3";
package micro.meter.handler; package micro.meter.handler;
option go_package = "github.com/unistack-org/micro/v3/meter/handler;handler"; option go_package = "go.unistack.org/micro/v3/meter/handler;handler";
import "api/annotations.proto"; import "api/annotations.proto";
import "openapiv2/annotations.proto"; import "openapiv3/annotations.proto";
import "codec/frame.proto"; import "codec/frame.proto";
service Meter { service Meter {
rpc Metrics(micro.codec.Frame) returns (micro.codec.Frame) { rpc Metrics(micro.codec.Frame) returns (micro.codec.Frame) {
option (micro.openapiv2.openapiv2_operation) = { option (micro.openapiv3.openapiv3_operation) = {
operation_id: "Metrics"; operation_id: "Metrics";
responses: { responses: {
key: "default"; default: {
value: { reference: {
description: "Error response"; _ref: "micro.codec.Frame";
schema: { };
json_schema: { };
ref: "micro.codec.Frame"; };
}
}
}
}
}; };
option (micro.api.http) = { get: "/metrics"; }; option (micro.api.http) = { get: "/metrics"; };
}; };

View File

@@ -1,22 +1,30 @@
// Code generated by protoc-gen-micro // Code generated by protoc-gen-go-micro. DO NOT EDIT.
// protoc-gen-go-micro version: v3.5.2
// source: handler.proto // source: handler.proto
package handler package handler
import ( import (
context "context" context "context"
api "github.com/unistack-org/micro/v3/api" api "go.unistack.org/micro/v3/api"
codec "github.com/unistack-org/micro/v3/codec" codec "go.unistack.org/micro/v3/codec"
) )
func NewMeterEndpoints() []*api.Endpoint { var (
return []*api.Endpoint{ MeterName = "Meter"
&api.Endpoint{
MeterEndpoints = []api.Endpoint{
{
Name: "Meter.Metrics", Name: "Meter.Metrics",
Path: []string{"/metrics"}, Path: []string{"/metrics"},
Method: []string{"GET"}, Method: []string{"GET"},
Handler: "rpc", Handler: "rpc",
}, },
} }
)
func NewMeterEndpoints() []api.Endpoint {
return MeterEndpoints
} }
type MeterServer interface { type MeterServer interface {

View File

@@ -1,12 +1,14 @@
// Code generated by protoc-gen-micro // Code generated by protoc-gen-go-micro. DO NOT EDIT.
// protoc-gen-go-micro version: v3.5.2
// source: handler.proto // source: handler.proto
package handler package handler
import ( import (
context "context" context "context"
api "github.com/unistack-org/micro/v3/api" api "go.unistack.org/micro/v3/api"
codec "github.com/unistack-org/micro/v3/codec" codec "go.unistack.org/micro/v3/codec"
server "github.com/unistack-org/micro/v3/server" server "go.unistack.org/micro/v3/server"
) )
type meterServer struct { type meterServer struct {
@@ -26,8 +28,8 @@ func RegisterMeterServer(s server.Server, sh MeterServer, opts ...server.Handler
} }
h := &meterServer{sh} h := &meterServer{sh}
var nopts []server.HandlerOption var nopts []server.HandlerOption
for _, endpoint := range NewMeterEndpoints() { for _, endpoint := range MeterEndpoints {
nopts = append(nopts, api.WithEndpoint(endpoint)) nopts = append(nopts, api.WithEndpoint(&endpoint))
} }
return s.Handle(s.NewHandler(&Meter{h}, append(nopts, opts...)...)) return s.Handle(s.NewHandler(&Meter{h}, append(nopts, opts...)...))
} }

View File

@@ -1,10 +1,11 @@
// Package meter is for instrumentation // Package meter is for instrumentation
package meter package meter // import "go.unistack.org/micro/v3/meter"
import ( import (
"io" "io"
"reflect"
"sort" "sort"
"strconv"
"strings"
"time" "time"
) )
@@ -27,17 +28,31 @@ var (
// Meter is an interface for collecting and instrumenting metrics // Meter is an interface for collecting and instrumenting metrics
type Meter interface { type Meter interface {
// Name returns meter name
Name() string Name() string
// Init initialize meter
Init(opts ...Option) error Init(opts ...Option) error
// Clone create meter copy with new options
Clone(opts ...Option) Meter
// Counter get or create counter
Counter(name string, labels ...string) Counter Counter(name string, labels ...string) Counter
// FloatCounter get or create float counter
FloatCounter(name string, labels ...string) FloatCounter FloatCounter(name string, labels ...string) FloatCounter
// Gauge get or create gauge
Gauge(name string, fn func() float64, labels ...string) Gauge Gauge(name string, fn func() float64, labels ...string) Gauge
// Set create new meter metrics set
Set(opts ...Option) Meter Set(opts ...Option) Meter
// Histogram get or create histogram
Histogram(name string, labels ...string) Histogram Histogram(name string, labels ...string) Histogram
// Summary get or create summary
Summary(name string, labels ...string) Summary Summary(name string, labels ...string) Summary
// SummaryExt get or create summary with spcified quantiles and window time
SummaryExt(name string, window time.Duration, quantiles []float64, labels ...string) Summary SummaryExt(name string, window time.Duration, quantiles []float64, labels ...string) Summary
// Write writes metrics to io.Writer
Write(w io.Writer, opts ...Option) error Write(w io.Writer, opts ...Option) error
// Options returns meter options
Options() Options Options() Options
// String return meter type
String() string String() string
} }
@@ -77,36 +92,62 @@ type Summary interface {
UpdateDuration(time.Time) UpdateDuration(time.Time)
} }
// sort labels alphabeticaly by label name
type byKey []string type byKey []string
func (k byKey) Len() int { return len(k) / 2 } func (k byKey) Len() int { return len(k) / 2 }
func (k byKey) Less(i, j int) bool { return k[i*2] < k[j*2] } func (k byKey) Less(i, j int) bool { return k[i*2] < k[j*2] }
func (k byKey) Swap(i, j int) { func (k byKey) Swap(i, j int) {
k[i*2], k[i*2+1], k[j*2], k[j*2+1] = k[j*2], k[j*2+1], k[i*2], k[i*2+1] k[i*2], k[j*2] = k[j*2], k[i*2]
k[i*2+1], k[j*2+1] = k[j*2+1], k[i*2+1]
} }
func Sort(slice *[]string) { // BuildLables used to sort labels and delete duplicates.
bk := byKey(*slice) // Last value wins in case of duplicate label keys.
if bk.Len() <= 1 { func BuildLabels(labels ...string) []string {
return if len(labels)%2 == 1 {
labels = labels[:len(labels)-1]
} }
sort.Sort(bk) sort.Sort(byKey(labels))
v := reflect.ValueOf(slice).Elem() return labels
cnt := 0 }
key := 0
val := 1 // BuildName used to combine metric with labels.
for key < v.Len() { // If labels count is odd, drop last element
if len(bk) > key+2 && bk[key] == bk[key+2] { func BuildName(name string, labels ...string) string {
key += 2 if len(labels)%2 == 1 {
val += 2 labels = labels[:len(labels)-1]
continue }
}
v.Index(cnt).Set(v.Index(key)) if len(labels) > 2 {
cnt++ sort.Sort(byKey(labels))
v.Index(cnt).Set(v.Index(val))
cnt++ idx := 0
key += 2 for {
val += 2 if labels[idx] == labels[idx+2] {
} copy(labels[idx:], labels[idx+2:])
v.SetLen(cnt) labels = labels[:len(labels)-2]
} else {
idx += 2
}
if idx+2 >= len(labels) {
break
}
}
}
var b strings.Builder
_, _ = b.WriteString(name)
_, _ = b.WriteRune('{')
for idx := 0; idx < len(labels); idx += 2 {
if idx > 0 {
_, _ = b.WriteRune(',')
}
_, _ = b.WriteString(labels[idx])
_, _ = b.WriteString(`=`)
_, _ = b.WriteString(strconv.Quote(labels[idx+1]))
}
_, _ = b.WriteRune('}')
return b.String()
} }

View File

@@ -14,11 +14,57 @@ func TestNoopMeter(t *testing.T) {
cnt.Inc() cnt.Inc()
} }
func TestLabelsSort(t *testing.T) { func testEq(a, b []string) bool {
ls := []string{"server", "http", "register", "mdns", "broker", "broker1", "broker", "broker2", "server", "tcp"} if len(a) != len(b) {
Sort(&ls) return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}
if ls[0] != "broker" || ls[1] != "broker2" { func TestBuildLabels(t *testing.T) {
t.Fatalf("sort error: %v", ls) type testData struct {
src []string
dst []string
}
data := []testData{
{
src: []string{"zerolabel", "value3", "firstlabel", "value2"},
dst: []string{"firstlabel", "value2", "zerolabel", "value3"},
},
}
for _, d := range data {
if !testEq(d.dst, BuildLabels(d.src...)) {
t.Fatalf("slices not properly sorted: %v %v", d.dst, d.src)
}
}
}
func TestBuildName(t *testing.T) {
data := map[string][]string{
`my_metric{firstlabel="value2",zerolabel="value3"}`: {
"my_metric",
"zerolabel", "value3", "firstlabel", "value2",
},
`my_metric{broker="broker2",register="mdns",server="tcp"}`: {
"my_metric",
"broker", "broker1", "broker", "broker2", "server", "http", "server", "tcp", "register", "mdns",
},
`my_metric{aaa="aaa"}`: {
"my_metric",
"aaa", "aaa",
},
}
for e, d := range data {
if x := BuildName(d[0], d[1:]...); x != e {
t.Fatalf("expect: %s, result: %s", e, x)
}
} }
} }

View File

@@ -15,6 +15,15 @@ func NewMeter(opts ...Option) Meter {
return &noopMeter{opts: NewOptions(opts...)} return &noopMeter{opts: NewOptions(opts...)}
} }
// Clone return old meter with new options
func (r *noopMeter) Clone(opts ...Option) Meter {
options := r.opts
for _, o := range opts {
o(&options)
}
return &noopMeter{opts: options}
}
func (r *noopMeter) Name() string { func (r *noopMeter) Name() string {
return r.opts.Name return r.opts.Name
} }

View File

@@ -3,7 +3,7 @@ package meter
import ( import (
"context" "context"
"github.com/unistack-org/micro/v3/logger" "go.unistack.org/micro/v3/logger"
) )
// Option powers the configuration for metrics implementations: // Option powers the configuration for metrics implementations:
@@ -51,6 +51,20 @@ func NewOptions(opt ...Option) Options {
return opts return opts
} }
// LabelPrefix sets the labels prefix
func LabelPrefix(pref string) Option {
return func(o *Options) {
o.LabelPrefix = pref
}
}
// MetricPrefix sets the metric prefix
func MetricPrefix(pref string) Option {
return func(o *Options) {
o.MetricPrefix = pref
}
}
// Context sets the metrics context // Context sets the metrics context
func Context(ctx context.Context) Option { func Context(ctx context.Context) Option {
return func(o *Options) { return func(o *Options) {

View File

@@ -1,13 +1,13 @@
package wrapper package wrapper // import "go.unistack.org/micro/v3/meter/wrapper"
import ( import (
"context" "context"
"fmt" "fmt"
"time" "time"
"github.com/unistack-org/micro/v3/client" "go.unistack.org/micro/v3/client"
"github.com/unistack-org/micro/v3/meter" "go.unistack.org/micro/v3/meter"
"github.com/unistack-org/micro/v3/server" "go.unistack.org/micro/v3/server"
) )
var ( var (

1
micro.go Normal file
View File

@@ -0,0 +1 @@
package micro // import "go.unistack.org/micro/v3"

View File

@@ -1,35 +0,0 @@
// +build ignore
// Package model is an interface for data modelling
package model
// Model provides an interface for data modelling
type Model interface {
// Initialise options
Init(...Option) error
// NewEntity creates a new entity to store or access
NewEntity(name string, value interface{}) Entity
// Create a value
Create(Entity) error
// Read values
Read(...ReadOption) ([]Entity, error)
// Update the value
Update(Entity) error
// Delete an entity
Delete(...DeleteOption) error
// Implementation of the model
String() string
}
type Entity interface {
// Unique id of the entity
Id() string
// Name of the entity
Name() string
// The value associated with the entity
Value() interface{}
// Attributes of the entity
Attributes() map[string]interface{}
// Read a value as a concrete type
Read(v interface{}) error
}

View File

@@ -1,41 +0,0 @@
// +build ignore
// Package model is an interface for data modelling
package model
import (
"github.com/unistack-org/micro/v3/codec"
"github.com/unistack-org/micro/v3/logger"
"github.com/unistack-org/micro/v3/store"
"github.com/unistack-org/micro/v3/sync"
)
type Options struct {
// Database to write to
Database string
// for serialising
Codec codec.Codec
// for locking
Sync sync.Sync
// for storage
Store store.Store
// for logger
Logger logger.Logger
}
type Option func(o *Options)
// Logger sets the logger
func Logger(l logger.Logger) Option {
return func(o *Options) {
o.Logger = l
}
}
type ReadOptions struct{}
type ReadOption func(o *ReadOptions)
type DeleteOptions struct{}
type DeleteOption func(o *DeleteOptions)

View File

@@ -1,9 +1,9 @@
// Package network is for creating internetworks // Package network is for creating internetworks
package network package network // import "go.unistack.org/micro/v3/network"
import ( import (
"github.com/unistack-org/micro/v3/client" "go.unistack.org/micro/v3/client"
"github.com/unistack-org/micro/v3/server" "go.unistack.org/micro/v3/server"
) )
// Error is network node errors // Error is network node errors

View File

@@ -1,13 +1,13 @@
package network package network
import ( import (
"github.com/google/uuid" "go.unistack.org/micro/v3/logger"
"github.com/unistack-org/micro/v3/logger" "go.unistack.org/micro/v3/meter"
"github.com/unistack-org/micro/v3/meter" "go.unistack.org/micro/v3/network/tunnel"
"github.com/unistack-org/micro/v3/network/tunnel" "go.unistack.org/micro/v3/proxy"
"github.com/unistack-org/micro/v3/proxy" "go.unistack.org/micro/v3/router"
"github.com/unistack-org/micro/v3/router" "go.unistack.org/micro/v3/tracer"
"github.com/unistack-org/micro/v3/tracer" "go.unistack.org/micro/v3/util/id"
) )
// Option func // Option func
@@ -27,8 +27,8 @@ type Options struct {
Tracer tracer.Tracer Tracer tracer.Tracer
// Tunnel used for transfer data // Tunnel used for transfer data
Tunnel tunnel.Tunnel Tunnel tunnel.Tunnel
// Id of the node // ID of the node
Id string ID string
// Name of the network // Name of the network
Name string Name string
// Address to bind to // Address to bind to
@@ -39,10 +39,10 @@ type Options struct {
Nodes []string Nodes []string
} }
// Id sets the id of the network node // ID sets the id of the network node
func Id(id string) Option { func ID(id string) Option {
return func(o *Options) { return func(o *Options) {
o.Id = id o.ID = id
} }
} }
@@ -119,7 +119,7 @@ func Tracer(t tracer.Tracer) Option {
// NewOptions returns network default options // NewOptions returns network default options
func NewOptions(opts ...Option) Options { func NewOptions(opts ...Option) Options {
options := Options{ options := Options{
Id: uuid.New().String(), ID: id.Must(),
Name: "go.micro", Name: "go.micro",
Address: ":0", Address: ":0",
Logger: logger.DefaultLogger, Logger: logger.DefaultLogger,

View File

@@ -8,9 +8,9 @@ import (
"sync" "sync"
"time" "time"
maddr "github.com/unistack-org/micro/v3/util/addr" maddr "go.unistack.org/micro/v3/util/addr"
mnet "github.com/unistack-org/micro/v3/util/net" mnet "go.unistack.org/micro/v3/util/net"
"github.com/unistack-org/micro/v3/util/rand" "go.unistack.org/micro/v3/util/rand"
) )
type memorySocket struct { type memorySocket struct {

View File

@@ -16,12 +16,14 @@ func TestMemoryTransport(t *testing.T) {
} }
defer l.Close() defer l.Close()
cherr := make(chan error, 1)
// accept // accept
go func() { go func() {
if err := l.Accept(func(sock Socket) { if nerr := l.Accept(func(sock Socket) {
for { for {
var m Message var m Message
if err := sock.Recv(&m); err != nil { if rerr := sock.Recv(&m); rerr != nil {
cherr <- rerr
return return
} }
if len(os.Getenv("INTEGRATION_TESTS")) == 0 { if len(os.Getenv("INTEGRATION_TESTS")) == 0 {
@@ -30,11 +32,12 @@ func TestMemoryTransport(t *testing.T) {
if cerr := sock.Send(&Message{ if cerr := sock.Send(&Message{
Body: []byte(`pong`), Body: []byte(`pong`),
}); cerr != nil { }); cerr != nil {
cherr <- cerr
return return
} }
} }
}); err != nil { }); nerr != nil {
t.Fatalf("Unexpected error accepting %v", err) cherr <- err
} }
}() }()
@@ -45,19 +48,24 @@ func TestMemoryTransport(t *testing.T) {
} }
defer c.Close() defer c.Close()
// send <=> receive select {
for i := 0; i < 3; i++ { case err := <-cherr:
if err := c.Send(&Message{ t.Fatal(err)
Body: []byte(`ping`), default:
}); err != nil { // send <=> receive
return for i := 0; i < 3; i++ {
} if err := c.Send(&Message{
var m Message Body: []byte(`ping`),
if err := c.Recv(&m); err != nil { }); err != nil {
return return
} }
if len(os.Getenv("INTEGRATION_TESTS")) == 0 { var m Message
t.Logf("Client Received %s", string(m.Body)) if err := c.Recv(&m); err != nil {
return
}
if len(os.Getenv("INTEGRATION_TESTS")) == 0 {
t.Logf("Client Received %s", string(m.Body))
}
} }
} }
} }

View File

@@ -5,10 +5,10 @@ import (
"crypto/tls" "crypto/tls"
"time" "time"
"github.com/unistack-org/micro/v3/codec" "go.unistack.org/micro/v3/codec"
"github.com/unistack-org/micro/v3/logger" "go.unistack.org/micro/v3/logger"
"github.com/unistack-org/micro/v3/meter" "go.unistack.org/micro/v3/meter"
"github.com/unistack-org/micro/v3/tracer" "go.unistack.org/micro/v3/tracer"
) )
// Options struct holds the transport options // Options struct holds the transport options

View File

@@ -1,11 +1,11 @@
// Package transport is an interface for synchronous connection based communication // Package transport is an interface for synchronous connection based communication
package transport package transport // import "go.unistack.org/micro/v3/network/transport"
import ( import (
"context" "context"
"time" "time"
"github.com/unistack-org/micro/v3/metadata" "go.unistack.org/micro/v3/metadata"
) )
var ( var (

View File

@@ -1,14 +1,15 @@
// Package broker is a tunnel broker // Package broker is a tunnel broker
package broker package broker // import "go.unistack.org/micro/v3/network/tunnel/broker"
import ( import (
"context" "context"
"fmt" "fmt"
"github.com/unistack-org/micro/v3/broker" "go.unistack.org/micro/v3/broker"
"github.com/unistack-org/micro/v3/logger" "go.unistack.org/micro/v3/logger"
"github.com/unistack-org/micro/v3/network/transport" "go.unistack.org/micro/v3/metadata"
"github.com/unistack-org/micro/v3/network/tunnel" "go.unistack.org/micro/v3/network/transport"
"go.unistack.org/micro/v3/network/tunnel"
) )
type tunBroker struct { type tunBroker struct {
@@ -24,7 +25,16 @@ type tunSubscriber struct {
opts broker.SubscribeOptions opts broker.SubscribeOptions
} }
type tunBatchSubscriber struct {
listener tunnel.Listener
handler broker.BatchHandler
closed chan bool
topic string
opts broker.SubscribeOptions
}
type tunEvent struct { type tunEvent struct {
err error
message *broker.Message message *broker.Message
topic string topic string
} }
@@ -62,6 +72,36 @@ func (t *tunBroker) Disconnect(ctx context.Context) error {
return t.tunnel.Close(ctx) return t.tunnel.Close(ctx)
} }
func (t *tunBroker) BatchPublish(ctx context.Context, msgs []*broker.Message, opts ...broker.PublishOption) error {
// TODO: this is probably inefficient, we might want to just maintain an open connection
// it may be easier to add broadcast to the tunnel
topicMap := make(map[string]tunnel.Session)
var err error
for _, msg := range msgs {
topic, _ := msg.Header.Get(metadata.HeaderTopic)
c, ok := topicMap[topic]
if !ok {
c, err = t.tunnel.Dial(ctx, topic, tunnel.DialMode(tunnel.Multicast))
if err != nil {
return err
}
defer c.Close()
topicMap[topic] = c
}
if err = c.Send(&transport.Message{
Header: msg.Header,
Body: msg.Body,
}); err != nil {
// msg.SetError(err)
return err
}
}
return nil
}
func (t *tunBroker) Publish(ctx context.Context, topic string, m *broker.Message, opts ...broker.PublishOption) error { func (t *tunBroker) Publish(ctx context.Context, topic string, m *broker.Message, opts ...broker.PublishOption) error {
// TODO: this is probably inefficient, we might want to just maintain an open connection // TODO: this is probably inefficient, we might want to just maintain an open connection
// it may be easier to add broadcast to the tunnel // it may be easier to add broadcast to the tunnel
@@ -77,6 +117,26 @@ func (t *tunBroker) Publish(ctx context.Context, topic string, m *broker.Message
}) })
} }
func (t *tunBroker) BatchSubscribe(ctx context.Context, topic string, h broker.BatchHandler, opts ...broker.SubscribeOption) (broker.Subscriber, error) {
l, err := t.tunnel.Listen(ctx, topic, tunnel.ListenMode(tunnel.Multicast))
if err != nil {
return nil, err
}
tunSub := &tunBatchSubscriber{
topic: topic,
handler: h,
opts: broker.NewSubscribeOptions(opts...),
closed: make(chan bool),
listener: l,
}
// start processing
go tunSub.run()
return tunSub, nil
}
func (t *tunBroker) Subscribe(ctx context.Context, topic string, h broker.Handler, opts ...broker.SubscribeOption) (broker.Subscriber, error) { func (t *tunBroker) Subscribe(ctx context.Context, topic string, h broker.Handler, opts ...broker.SubscribeOption) (broker.Subscriber, error) {
l, err := t.tunnel.Listen(ctx, topic, tunnel.ListenMode(tunnel.Multicast)) l, err := t.tunnel.Listen(ctx, topic, tunnel.ListenMode(tunnel.Multicast))
if err != nil { if err != nil {
@@ -101,6 +161,51 @@ func (t *tunBroker) String() string {
return "tunnel" return "tunnel"
} }
func (t *tunBatchSubscriber) run() {
for {
// accept a new connection
c, err := t.listener.Accept()
if err != nil {
select {
case <-t.closed:
return
default:
continue
}
}
// receive message
m := new(transport.Message)
if err := c.Recv(m); err != nil {
if logger.V(logger.ErrorLevel) {
logger.Error(t.opts.Context, err.Error())
}
if err = c.Close(); err != nil {
if logger.V(logger.ErrorLevel) {
logger.Error(t.opts.Context, err.Error())
}
}
continue
}
// close the connection
c.Close()
evts := broker.Events{&tunEvent{
topic: t.topic,
message: &broker.Message{
Header: m.Header,
Body: m.Body,
},
}}
// handle the message
go func() {
_ = t.handler(evts)
}()
}
}
func (t *tunSubscriber) run() { func (t *tunSubscriber) run() {
for { for {
// accept a new connection // accept a new connection
@@ -132,13 +237,33 @@ func (t *tunSubscriber) run() {
c.Close() c.Close()
// handle the message // handle the message
go t.handler(&tunEvent{ go func() {
topic: t.topic, _ = t.handler(&tunEvent{
message: &broker.Message{ topic: t.topic,
Header: m.Header, message: &broker.Message{
Body: m.Body, Header: m.Header,
}, Body: m.Body,
}) },
})
}()
}
}
func (t *tunBatchSubscriber) Options() broker.SubscribeOptions {
return t.opts
}
func (t *tunBatchSubscriber) Topic() string {
return t.topic
}
func (t *tunBatchSubscriber) Unsubscribe(ctx context.Context) error {
select {
case <-t.closed:
return nil
default:
close(t.closed)
return t.listener.Close()
} }
} }
@@ -173,7 +298,11 @@ func (t *tunEvent) Ack() error {
} }
func (t *tunEvent) Error() error { func (t *tunEvent) Error() error {
return nil return t.err
}
func (t *tunEvent) SetError(err error) {
t.err = err
} }
// NewBroker returns new tunnel broker // NewBroker returns new tunnel broker

View File

@@ -3,11 +3,11 @@ package tunnel
import ( import (
"time" "time"
"github.com/google/uuid" "go.unistack.org/micro/v3/logger"
"github.com/unistack-org/micro/v3/logger" "go.unistack.org/micro/v3/meter"
"github.com/unistack-org/micro/v3/meter" "go.unistack.org/micro/v3/network/transport"
"github.com/unistack-org/micro/v3/network/transport" "go.unistack.org/micro/v3/tracer"
"github.com/unistack-org/micro/v3/tracer" "go.unistack.org/micro/v3/util/id"
) )
var ( var (
@@ -164,7 +164,7 @@ func DialWait(b bool) DialOption {
// NewOptions returns router default options with filled values // NewOptions returns router default options with filled values
func NewOptions(opts ...Option) Options { func NewOptions(opts ...Option) Options {
options := Options{ options := Options{
ID: uuid.New().String(), ID: id.Must(),
Address: DefaultAddress, Address: DefaultAddress,
Token: DefaultToken, Token: DefaultToken,
Logger: logger.DefaultLogger, Logger: logger.DefaultLogger,

View File

@@ -1,8 +1,8 @@
package transport package transport
import ( import (
"github.com/unistack-org/micro/v3/network/transport" "go.unistack.org/micro/v3/network/transport"
"github.com/unistack-org/micro/v3/network/tunnel" "go.unistack.org/micro/v3/network/tunnel"
) )
type tunListener struct { type tunListener struct {

View File

@@ -1,12 +1,12 @@
// Package transport provides a tunnel transport // Package transport provides a tunnel transport
package transport package transport // import "go.unistack.org/micro/v3/network/tunnel/transport"
import ( import (
"context" "context"
"fmt" "fmt"
"github.com/unistack-org/micro/v3/network/transport" "go.unistack.org/micro/v3/network/transport"
"github.com/unistack-org/micro/v3/network/tunnel" "go.unistack.org/micro/v3/network/tunnel"
) )
type tunTransport struct { type tunTransport struct {
@@ -37,7 +37,7 @@ func (t *tunTransport) Init(opts ...transport.Option) error {
// get the transport // get the transport
tr, ok := t.options.Context.Value(transportKey{}).(transport.Transport) tr, ok := t.options.Context.Value(transportKey{}).(transport.Transport)
if ok { if ok {
tun.Init(tunnel.Transport(tr)) _ = tun.Init(tunnel.Transport(tr))
} }
// set the tunnel // set the tunnel

View File

@@ -1,12 +1,12 @@
// Package tunnel provides gre network tunnelling // Package tunnel provides gre network tunnelling
package tunnel package tunnel // import "go.unistack.org/micro/v3/network/transport/tunnel"
import ( import (
"context" "context"
"errors" "errors"
"time" "time"
"github.com/unistack-org/micro/v3/network/transport" "go.unistack.org/micro/v3/network/transport"
) )
// DefaultTunnel contains default tunnel implementation // DefaultTunnel contains default tunnel implementation

View File

@@ -5,18 +5,18 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/unistack-org/micro/v3/auth" "go.unistack.org/micro/v3/auth"
"github.com/unistack-org/micro/v3/broker" "go.unistack.org/micro/v3/broker"
"github.com/unistack-org/micro/v3/client" "go.unistack.org/micro/v3/client"
"github.com/unistack-org/micro/v3/config" "go.unistack.org/micro/v3/config"
"github.com/unistack-org/micro/v3/logger" "go.unistack.org/micro/v3/logger"
"github.com/unistack-org/micro/v3/metadata" "go.unistack.org/micro/v3/metadata"
"github.com/unistack-org/micro/v3/meter" "go.unistack.org/micro/v3/meter"
"github.com/unistack-org/micro/v3/register" "go.unistack.org/micro/v3/register"
"github.com/unistack-org/micro/v3/router" "go.unistack.org/micro/v3/router"
"github.com/unistack-org/micro/v3/server" "go.unistack.org/micro/v3/server"
"github.com/unistack-org/micro/v3/store" "go.unistack.org/micro/v3/store"
"github.com/unistack-org/micro/v3/tracer" "go.unistack.org/micro/v3/tracer"
) )
// Options for micro service // Options for micro service

View File

@@ -1,5 +1,5 @@
// Package http enables the http profiler // Package http enables the http profiler
package http package http // import "go.unistack.org/micro/v3/profiler/http"
import ( import (
"context" "context"
@@ -7,7 +7,7 @@ import (
"net/http/pprof" "net/http/pprof"
"sync" "sync"
profile "github.com/unistack-org/micro/v3/profiler" profile "go.unistack.org/micro/v3/profiler"
) )
type httpProfile struct { type httpProfile struct {

View File

@@ -1,5 +1,5 @@
// Package pprof provides a pprof profiler which writes output to /tmp/[name].{cpu,mem}.pprof // Package pprof provides a pprof profiler which writes output to /tmp/[name].{cpu,mem}.pprof
package pprof package pprof // import "go.unistack.org/micro/v3/profiler/pprof"
import ( import (
"os" "os"
@@ -9,7 +9,7 @@ import (
"sync" "sync"
"time" "time"
profile "github.com/unistack-org/micro/v3/profiler" profile "go.unistack.org/micro/v3/profiler"
) )
type profiler struct { type profiler struct {
@@ -31,7 +31,7 @@ func (p *profiler) writeHeap(f *os.File) {
select { select {
case <-t.C: case <-t.C:
runtime.GC() runtime.GC()
pprof.WriteHeapProfile(f) _ = pprof.WriteHeapProfile(f)
case <-p.exit: case <-p.exit:
return return
} }

View File

@@ -1,5 +1,5 @@
// Package profiler is for profilers // Package profiler is for profilers
package profiler package profiler // import "go.unistack.org/micro/v3/profiler"
// Profiler interface // Profiler interface
type Profiler interface { type Profiler interface {

View File

@@ -2,11 +2,11 @@
package proxy package proxy
import ( import (
"github.com/unistack-org/micro/v3/client" "go.unistack.org/micro/v3/client"
"github.com/unistack-org/micro/v3/logger" "go.unistack.org/micro/v3/logger"
"github.com/unistack-org/micro/v3/meter" "go.unistack.org/micro/v3/meter"
"github.com/unistack-org/micro/v3/router" "go.unistack.org/micro/v3/router"
"github.com/unistack-org/micro/v3/tracer" "go.unistack.org/micro/v3/tracer"
) )
// Options for proxy // Options for proxy

View File

@@ -1,10 +1,10 @@
// Package proxy is a transparent proxy built on the micro/server // Package proxy is a transparent proxy built on the micro/server
package proxy package proxy // import "go.unistack.org/micro/v3/proxy"
import ( import (
"context" "context"
"github.com/unistack-org/micro/v3/server" "go.unistack.org/micro/v3/server"
) )
// DefaultEndpoint holds default proxy address // DefaultEndpoint holds default proxy address

View File

@@ -6,7 +6,7 @@ import (
"unicode" "unicode"
"unicode/utf8" "unicode/utf8"
"github.com/unistack-org/micro/v3/metadata" "go.unistack.org/micro/v3/metadata"
) )
// ExtractValue from reflect.Type from specified depth // ExtractValue from reflect.Type from specified depth

View File

@@ -6,8 +6,8 @@ import (
"sync" "sync"
"time" "time"
"github.com/google/uuid" "go.unistack.org/micro/v3/logger"
"github.com/unistack-org/micro/v3/logger" "go.unistack.org/micro/v3/util/id"
) )
var ( var (
@@ -378,13 +378,16 @@ func (m *memory) ListServices(ctx context.Context, opts ...ListOption) ([]*Servi
} }
func (m *memory) Watch(ctx context.Context, opts ...WatchOption) (Watcher, error) { func (m *memory) Watch(ctx context.Context, opts ...WatchOption) (Watcher, error) {
id, err := id.New()
if err != nil {
return nil, err
}
wo := NewWatchOptions(opts...) wo := NewWatchOptions(opts...)
// construct the watcher // construct the watcher
w := &watcher{ w := &watcher{
exit: make(chan bool), exit: make(chan bool),
res: make(chan *Result), res: make(chan *Result),
id: uuid.New().String(), id: id,
wo: wo, wo: wo,
} }

View File

@@ -5,9 +5,9 @@ import (
"crypto/tls" "crypto/tls"
"time" "time"
"github.com/unistack-org/micro/v3/logger" "go.unistack.org/micro/v3/logger"
"github.com/unistack-org/micro/v3/meter" "go.unistack.org/micro/v3/meter"
"github.com/unistack-org/micro/v3/tracer" "go.unistack.org/micro/v3/tracer"
) )
// Options holds options for register // Options holds options for register
@@ -44,7 +44,7 @@ func NewOptions(opts ...Option) Options {
return options return options
} }
// nolint: golint // nolint: golint,revive
// RegisterOptions holds options for register method // RegisterOptions holds options for register method
type RegisterOptions struct { type RegisterOptions struct {
Context context.Context Context context.Context
@@ -197,7 +197,7 @@ func TLSConfig(t *tls.Config) Option {
} }
} }
// nolint: golint // nolint: golint,revive
// RegisterAttempts specifies register atempts count // RegisterAttempts specifies register atempts count
func RegisterAttempts(t int) RegisterOption { func RegisterAttempts(t int) RegisterOption {
return func(o *RegisterOptions) { return func(o *RegisterOptions) {
@@ -205,7 +205,7 @@ func RegisterAttempts(t int) RegisterOption {
} }
} }
// nolint: golint // nolint: golint,revive
// RegisterTTL specifies register ttl // RegisterTTL specifies register ttl
func RegisterTTL(t time.Duration) RegisterOption { func RegisterTTL(t time.Duration) RegisterOption {
return func(o *RegisterOptions) { return func(o *RegisterOptions) {
@@ -213,7 +213,7 @@ func RegisterTTL(t time.Duration) RegisterOption {
} }
} }
// nolint: golint // nolint: golint,revive
// RegisterContext sets the register context // RegisterContext sets the register context
func RegisterContext(ctx context.Context) RegisterOption { func RegisterContext(ctx context.Context) RegisterOption {
return func(o *RegisterOptions) { return func(o *RegisterOptions) {
@@ -221,7 +221,7 @@ func RegisterContext(ctx context.Context) RegisterOption {
} }
} }
// nolint: golint // nolint: golint,revive
// RegisterDomain secifies register domain // RegisterDomain secifies register domain
func RegisterDomain(d string) RegisterOption { func RegisterDomain(d string) RegisterOption {
return func(o *RegisterOptions) { return func(o *RegisterOptions) {

View File

@@ -1,11 +1,11 @@
// Package register is an interface for service discovery // Package register is an interface for service discovery
package register package register // import "go.unistack.org/micro/v3/register"
import ( import (
"context" "context"
"errors" "errors"
"github.com/unistack-org/micro/v3/metadata" "go.unistack.org/micro/v3/metadata"
) )
const ( const (
@@ -68,8 +68,8 @@ type Endpoint struct {
// Option func signature // Option func signature
type Option func(*Options) type Option func(*Options)
// nolint: golint,revive
// RegisterOption option is used to register service // RegisterOption option is used to register service
// nolint: golint
type RegisterOption func(*RegisterOptions) type RegisterOption func(*RegisterOptions)
// WatchOption option is used to watch service changes // WatchOption option is used to watch service changes

View File

@@ -1,5 +1,5 @@
// Package dns resolves names to dns records // Package dns resolves names to dns records
package dns package dns // import "go.unistack.org/micro/v3/resolver/dns"
import ( import (
"context" "context"
@@ -7,7 +7,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/unistack-org/micro/v3/resolver" "go.unistack.org/micro/v3/resolver"
) )
// Resolver is a DNS network resolve // Resolver is a DNS network resolve

View File

@@ -1,11 +1,11 @@
// Package dnssrv resolves names to dns srv records // Package dnssrv resolves names to dns srv records
package dnssrv package dnssrv // import "go.unistack.org/micro/v3/resolver/dnssrv"
import ( import (
"fmt" "fmt"
"net" "net"
"github.com/unistack-org/micro/v3/resolver" "go.unistack.org/micro/v3/resolver"
) )
// Resolver is a DNS network resolve // Resolver is a DNS network resolve

View File

@@ -1,5 +1,5 @@
// Package http resolves names to network addresses using a http request // Package http resolves names to network addresses using a http request
package http package http // import "go.unistack.org/micro/v3/resolver/http"
import ( import (
"encoding/json" "encoding/json"
@@ -8,9 +8,10 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"github.com/unistack-org/micro/v3/resolver" "go.unistack.org/micro/v3/resolver"
) )
// nolint: golint,revive
// HTTPResolver is a HTTP network resolver // HTTPResolver is a HTTP network resolver
type HTTPResolver struct { type HTTPResolver struct {
// Proto if not set, defaults to http // Proto if not set, defaults to http
@@ -53,6 +54,7 @@ func (r *HTTPResolver) Resolve(name string) ([]*resolver.Record, error) {
q.Set("name", name) q.Set("name", name)
uri.RawQuery = q.Encode() uri.RawQuery = q.Encode()
// nolint: noctx
rsp, err := http.Get(uri.String()) rsp, err := http.Get(uri.String())
if err != nil { if err != nil {
return nil, err return nil, err

Some files were not shown because too many files have changed in this diff Show More