Compare commits

..

104 Commits

Author SHA1 Message Date
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
0d1ef31764 client: change AuthToken option signature
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-09 10:47:40 +03:00
d49afa230f logger: add omit logger
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-05 23:04:20 +03:00
e545eb4e13 logger: add wrapper support
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-05 22:32:47 +03:00
f28b107372 broker: fix RawMessage marshal
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-01 23:23:01 +03:00
c592fabe2a minor fixes
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-01 15:56:22 +03:00
eb107020c7 broker: replace Message.Body []byte slice to RawMessage
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-01 15:11:17 +03:00
bd4d4c363e flow improvements (#52)
* flow improvements

Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-06-30 17:50:58 +03:00
2a548634fd config: add Save/Load options
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-06-20 23:57:13 +03:00
598dddc476 util/reflect: fix time.Time StructFields parsing
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-06-19 15:58:36 +03:00
887b48f1e7 util/reflect: improve StructFields func
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-06-17 12:53:23 +03:00
6e55d07636 client: allow to publish body only
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-06-13 12:20:35 +03:00
919520219c client: WithBodyOnly publish option
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-06-11 14:14:41 +03:00
60a5e737f8 util/reflect: return pointer from helper funcs
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-05-25 22:44:22 +03:00
34f0b209cc codec: add ability to control codec via struct tags
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-05-25 22:20:39 +03:00
ba8e1889fe dependabot
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-05-16 17:18:56 +03:00
dae5c57a60 Create dependabot.yml 2021-05-15 14:46:22 +03:00
ea590d57df meter/wrapper: add inflight request/message count (#47)
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-05-10 17:59:40 +03:00
Renovate Bot
9aa6969836 fix(deps): update golang.org/x/net commit hash to 4163338 2021-05-10 14:29:32 +00:00
Renovate Bot
c00c705c24 fix(deps): update golang.org/x/net commit hash to 16afe75 2021-05-08 09:02:01 +00:00
Renovate Bot
0239f795d8 fix(deps): update golang.org/x/net commit hash to 7fd8e65 2021-05-03 10:07:41 +00:00
Renovate Bot
e69b43881d fix(deps): update golang.org/x/net commit hash to f8dd838 2021-05-01 23:20:59 +00:00
3a48a613fe not fail on lint now
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-04-27 08:36:11 +03:00
86626c5922 fieldalignment of all structs to save memory
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-04-27 08:32:47 +03:00
ee11f39a2f fieldaligment
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-04-27 00:03:18 +03:00
3bdfdd8fd2 meter: fix labels
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-04-27 00:03:18 +03:00
6dfdff7fd8 fieldaligment
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-04-27 00:03:18 +03:00
00a4785df3 fixup
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-04-27 00:03:18 +03:00
Renovate Bot
bae3b0ef94 fix(deps): update golang.org/x/net commit hash to 5f58ad6 2021-04-23 23:52:34 +00:00
Renovate Bot
89b0565062 fix(deps): update golang.org/x/net commit hash to 4e50805 2021-04-22 03:04:18 +00:00
1f8b0aeb61 store: remove unused Value type
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-04-22 00:57:06 +03:00
Renovate Bot
5b6f849e0a fix(deps): update golang.org/x/net commit hash to 798c215 2021-04-20 23:13:59 +00:00
Renovate Bot
3b416fffde fix(deps): update golang.org/x/net commit hash to d25e304 2021-04-20 15:04:17 +00:00
3a60103aed server: drop Internal option
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-04-20 12:45:14 +03:00
41837a67f8 register: drop verbose values export
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-04-20 12:39:21 +03:00
852f19598d util/reflect: fix protobuf field name detection
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-04-19 11:34:28 +03:00
6537b35773 util/reflect: add interface merging
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-04-19 01:19:37 +03:00
b733f1316f remove stale generate stuff
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-04-16 17:36:27 +03:00
Renovate Bot
840af5574c fix(deps): update golang.org/x/net commit hash to e915ea6 2021-04-16 00:56:52 +00:00
Renovate Bot
56e5b7001c fix(deps): update golang.org/x/net commit hash to 0645797 2021-04-14 21:41:15 +00:00
Renovate Bot
11dc6fd752 fix(deps): update golang.org/x/net commit hash to afb366f 2021-04-10 11:09:36 +00:00
a2695d8699 util/reflect: rewrite struct merging with map
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-04-10 01:22:40 +03:00
618421de05 client: allow to set content-type for call
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-04-09 23:09:12 +03:00
Renovate Bot
30baaabd9f fix(deps): update golang.org/x/net commit hash to a5a99cb 2021-04-05 19:46:46 +00:00
df5bce1191 util/reflect: fix StructURLValues
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-04-03 11:50:23 +03:00
Renovate Bot
089d0fe4df fix(deps): update golang.org/x/net commit hash to 0fccb6f 2021-03-31 22:50:38 +00:00
a06f535303 util/reflect: add StructURLValues func
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-04-01 00:30:26 +03:00
Renovate Bot
eba586a329 fix(deps): update golang.org/x/net commit hash to cb1fcc7 2021-03-31 09:19:27 +00:00
Renovate Bot
d74a8645e8 fix(deps): update golang.org/x/net commit hash to e572328 2021-03-31 00:52:53 +00:00
Renovate Bot
5a00786192 fix(deps): update golang.org/x/net commit hash to cd0ac97 2021-03-30 22:28:38 +00:00
Renovate Bot
b3e9941634 fix(deps): update golang.org/x/net commit hash to c8897c2 2021-03-30 16:02:28 +00:00
Renovate Bot
a5a5904302 fix(deps): update golang.org/x/net commit hash to 22f4162 2021-03-30 11:44:00 +00:00
Renovate Bot
a59832e57e fix(deps): update golang.org/x/net commit hash to df645c7 2021-03-30 05:11:12 +00:00
0e42033e7f meter/handler: more idiomatic option handling
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-03-29 17:51:44 +03:00
52d8255974 service init with own context
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-03-28 23:42:02 +03:00
9830cb48a9 fix compilation
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-03-28 19:31:03 +03:00
92d7ab2105 regen handlers
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-03-28 19:28:01 +03:00
d2935ef399 meter/handler: fix proto and generated code
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-03-28 19:18:11 +03:00
ce4c96ae0a server/health: add health check handler
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-03-28 19:18:11 +03:00
Renovate Bot
14026d15be fix(deps): update golang.org/x/net commit hash to 61e0566 2021-03-27 01:29:30 +00:00
Renovate Bot
2df0c7643e fix(deps): update golang.org/x/net commit hash to 6b15177 2021-03-26 19:17:30 +00:00
e13c2c48fd client: use router.DefaultRouter in NewOptions helper
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-03-26 17:45:55 +03:00
8db55d2e55 router: use dns as default router
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-03-26 17:43:39 +03:00
ed61cad961 meter/handler: regen
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-03-26 15:45:09 +03:00
040fc4548f client: add TLSConfig option
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-03-26 15:44:34 +03:00
6189a1b980 add SkipEndpoints for wrappers
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-03-25 23:30:38 +03:00
eb2a450a7b meter/handler: fix func signature
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-03-25 12:36:48 +03:00
Renovate Bot
d2a30a5da1 fix(deps): update golang.org/x/net commit hash to d1beb07 2021-03-24 23:28:38 +00:00
65889c66f6 meter/wrapper: add DefaultSkipEndpoints
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-03-25 00:06:38 +03:00
131 changed files with 5271 additions and 1907 deletions

19
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,19 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
# Maintain dependencies for GitHub Actions
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"
# Maintain dependencies for Golang
- package-ecosystem: "gomod"
directory: "/"
schedule:
interval: "daily"

15
.github/generate.sh vendored
View File

@@ -1,15 +0,0 @@
#!/bin/bash -e
find . -type f -name '*.pb.*.go' -o -name '*.pb.go' -a ! -name 'message.pb.go' -delete
PROTOS=$(find . -type f -name '*.proto' | grep -v proto/google/api)
mkdir -p proto/google/api
curl -s -o proto/google/api/annotations.proto -L https://raw.githubusercontent.com/googleapis/googleapis/master/google/api/annotations.proto
curl -s -o proto/google/api/http.proto -L https://raw.githubusercontent.com/googleapis/googleapis/master/google/api/http.proto
for PROTO in $PROTOS; do
echo $PROTO
protoc -I./proto -I. -I$(dirname $PROTO) --go-grpc_out=paths=source_relative:. --go_out=paths=source_relative:. --micro_out=paths=source_relative:. $PROTO
done
rm -r proto

20
.github/renovate.json vendored
View File

@@ -1,20 +0,0 @@
{
"extends": [
"config:base"
],
"postUpdateOptions": ["gomodTidy"],
"packageRules": [
{
"matchUpdateTypes": ["minor", "patch", "pin", "digest"],
"automerge": true
},
{
"groupName": "all deps",
"separateMajorMinor": true,
"groupSlug": "all",
"packagePatterns": [
"*"
]
}
]
}

13
.github/stale.sh vendored
View File

@@ -1,13 +0,0 @@
#!/bin/bash -ex
export PATH=$PATH:$(pwd)/bin
export GO111MODULE=on
export GOBIN=$(pwd)/bin
#go get github.com/rvflash/goup@v0.4.1
#goup -v ./...
#go get github.com/psampaz/go-mod-outdated@v0.6.0
go list -u -m -mod=mod -json all | go-mod-outdated -update -direct -ci || true
#go list -u -m -json all | go-mod-outdated -update

View File

@@ -53,7 +53,7 @@ jobs:
continue-on-error: true
with:
# Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
version: v1.30
version: v1.39
# Optional: working directory, useful for monorepos
# working-directory: somedir
# Optional: golangci-lint command line arguments.

View File

@@ -53,7 +53,7 @@ jobs:
continue-on-error: true
with:
# Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
version: v1.30
version: v1.39
# Optional: working directory, useful for monorepos
# working-directory: somedir
# Optional: golangci-lint command line arguments.

View File

@@ -1,30 +1,44 @@
run:
concurrency: 4
deadline: 5m
modules-download-mode: readonly
skip-files:
- ".*\\.pb\\.go$"
- ".*\\.pb\\.micro\\.go$"
issues-exit-code: 1
tests: true
linters-settings:
govet:
check-shadowing: true
enable:
- fieldalignment
linters:
disable-all: false
enable-all: false
enable:
- megacheck
- staticcheck
- deadcode
- varcheck
- gosimple
- unused
- prealloc
- scopelint
- gocritic
- goimports
- unconvert
- govet
- nakedret
- deadcode
- errcheck
- govet
- ineffassign
- staticcheck
- structcheck
- gosec
disable:
- maligned
- interfacer
- typecheck
- dupl
- unused
- varcheck
- bodyclose
- gci
- goconst
- gocritic
- gosimple
- gofmt
- gofumpt
- goimports
- golint
- gosec
- makezero
- misspell
- nakedret
- nestif
- nilerr
- noctx
- prealloc
- unconvert
- unparam
disable-all: false

View File

@@ -149,5 +149,4 @@ func TestValidate(t *testing.T) {
if err := Validate(epPcreInvalid); err == nil {
t.Fatalf("invalid pcre %v", epPcreInvalid.Path[0])
}
}

View File

@@ -6,10 +6,8 @@ import (
"github.com/unistack-org/micro/v3/logger"
)
var (
// DefaultMaxRecvSize specifies max recv size for handler
DefaultMaxRecvSize int64 = 1024 * 1024 * 100 // 10Mb
)
// DefaultMaxRecvSize specifies max recv size for handler
var DefaultMaxRecvSize int64 = 1024 * 1024 * 100 // 10Mb
// Options struct holds handler options
type Options struct {

View File

@@ -15,12 +15,12 @@ import (
// NewResolver creates new subdomain api resolver
func NewResolver(parent resolver.Resolver, opts ...resolver.Option) resolver.Resolver {
options := resolver.NewOptions(opts...)
return &subdomainResolver{options, parent}
return &subdomainResolver{opts: options, Resolver: parent}
}
type subdomainResolver struct {
opts resolver.Options
resolver.Resolver
opts resolver.Options
}
// Resolve resolve endpoint based on subdomain

View File

@@ -19,9 +19,7 @@ type vpathResolver struct {
opts resolver.Options
}
var (
re = regexp.MustCompile("^v[0-9]+$")
)
var re = regexp.MustCompile("^v[0-9]+$")
// Resolve endpoint
func (r *vpathResolver) Resolve(req *http.Request, opts ...resolver.ResolveOption) (*resolver.Endpoint, error) {

View File

@@ -7,10 +7,8 @@ import (
"github.com/unistack-org/micro/v3/api"
)
var (
// DefaultRouter contains default router implementation
DefaultRouter Router
)
// DefaultRouter contains default router implementation
var DefaultRouter Router
// Router is used to determine an endpoint for a request
type Router interface {

View File

@@ -24,11 +24,11 @@ func TestVerify(t *testing.T) {
}
tt := []struct {
Name string
Rules []*Rule
Error error
Account *Account
Resource *Resource
Error error
Name string
Rules []*Rule
}{
{
Name: "NoRules",

View File

@@ -3,48 +3,128 @@ package broker
import (
"context"
"errors"
"github.com/unistack-org/micro/v3/metadata"
)
// DefaultBroker default memory broker
var DefaultBroker Broker = NewBroker()
var (
// DefaultBroker default broker
DefaultBroker Broker = NewBroker()
// 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.
type Broker interface {
// Name returns broker instance name
Name() string
Init(...Option) error
// Init initilize broker
Init(opts ...Option) error
// Options returns broker options
Options() Options
// Address return configured address
Address() string
Connect(context.Context) error
Disconnect(context.Context) error
Publish(context.Context, string, *Message, ...PublishOption) error
Subscribe(context.Context, string, Handler, ...SubscribeOption) (Subscriber, error)
// Connect connects to broker
Connect(ctx context.Context) error
// Disconnect disconnect from broker
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
}
// Handler is used to process messages via a subscription of a topic.
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
type Event interface {
// Topic returns event topic
Topic() string
// Message returns broker message
Message() *Message
// Ack acknowledge message
Ack() error
// Error returns message error (like decoding errors or some other)
Error() error
// SetError set event processing error
SetError(err error)
}
// 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
}
// Message is used to transfer data
type Message struct {
Header metadata.Metadata // contains message metadata
Body []byte // contains message body
// Header contains message metadata
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
type Subscriber interface {
// Options returns subscriber options
Options() SubscribeOptions
// Topic returns topic for subscription
Topic() string
Unsubscribe(context.Context) error
// Unsubscribe from topic
Unsubscribe(ctx context.Context) error
}

View File

@@ -2,38 +2,39 @@ package broker
import (
"context"
"errors"
"sync"
"github.com/google/uuid"
"github.com/unistack-org/micro/v3/logger"
"github.com/unistack-org/micro/v3/metadata"
maddr "github.com/unistack-org/micro/v3/util/addr"
mnet "github.com/unistack-org/micro/v3/util/net"
"github.com/unistack-org/micro/v3/util/rand"
)
type memoryBroker struct {
opts Options
Subscribers map[string][]*memorySubscriber
subscribers map[string][]*memorySubscriber
addr string
opts Options
sync.RWMutex
connected bool
}
type memoryEvent struct {
opts Options
err error
message interface{}
topic string
opts Options
}
type memorySubscriber struct {
opts SubscribeOptions
ctx context.Context
exit chan bool
handler Handler
id string
topic string
ctx context.Context
exit chan bool
handler Handler
batchhandler BatchHandler
id string
topic string
opts SubscribeOptions
}
func (m *memoryBroker) Options() Options {
@@ -77,7 +78,6 @@ func (m *memoryBroker) Disconnect(ctx context.Context) error {
}
m.connected = false
return nil
}
@@ -92,67 +92,190 @@ func (m *memoryBroker) Publish(ctx context.Context, topic string, msg *Message,
m.RLock()
if !m.connected {
m.RUnlock()
return errors.New("not connected")
return ErrNotConnected
}
subs, ok := m.Subscribers[topic]
m.RUnlock()
if !ok {
return nil
}
var v interface{}
if m.opts.Codec != nil {
options := NewPublishOptions(opts...)
vs := make([]msgWrapper, 0, 1)
if m.opts.Codec == nil || options.BodyOnly {
topic, _ := msg.Header.Get(metadata.HeaderTopic)
vs = append(vs, msgWrapper{topic: topic, body: msg})
} else {
topic, _ := msg.Header.Get(metadata.HeaderTopic)
buf, err := m.opts.Codec.Marshal(msg)
if err != nil {
return err
}
v = buf
vs = append(vs, msgWrapper{topic: topic, body: buf})
}
return m.publish(ctx, vs, opts...)
}
type msgWrapper struct {
topic string
body interface{}
}
func (m *memoryBroker) BatchPublish(ctx context.Context, msgs []*Message, opts ...PublishOption) error {
m.RLock()
if !m.connected {
m.RUnlock()
return ErrNotConnected
}
m.RUnlock()
options := NewPublishOptions(opts...)
vs := make([]msgWrapper, 0, len(msgs))
if m.opts.Codec == nil || options.BodyOnly {
for _, msg := range msgs {
topic, _ := msg.Header.Get(metadata.HeaderTopic)
vs = append(vs, msgWrapper{topic: topic, body: msg})
}
} else {
v = msg
for _, msg := range msgs {
topic, _ := msg.Header.Get(metadata.HeaderTopic)
buf, err := m.opts.Codec.Marshal(msg)
if err != nil {
return err
}
vs = append(vs, msgWrapper{topic: topic, body: buf})
}
}
p := &memoryEvent{
topic: topic,
message: v,
opts: m.opts,
return m.publish(ctx, vs, opts...)
}
func (m *memoryBroker) publish(ctx context.Context, vs []msgWrapper, opts ...PublishOption) error {
var err error
msgTopicMap := make(map[string]Events)
for _, v := range vs {
p := &memoryEvent{
topic: v.topic,
message: v.body,
opts: m.opts,
}
msgTopicMap[p.topic] = append(msgTopicMap[p.topic], p)
}
beh := m.opts.BatchErrorHandler
eh := m.opts.ErrorHandler
for _, sub := range subs {
if err := sub.handler(p); err != nil {
p.err = err
if sub.opts.ErrorHandler != nil {
eh = sub.opts.ErrorHandler
}
if eh != nil {
eh(p)
} else if m.opts.Logger.V(logger.ErrorLevel) {
m.opts.Logger.Error(m.opts.Context, err.Error())
}
for t, ms := range msgTopicMap {
m.RLock()
subs, ok := m.subscribers[t]
m.RUnlock()
if !ok {
continue
}
for _, sub := range subs {
// batch processing
if sub.batchhandler != nil {
if err = sub.batchhandler(ms); err != nil {
ms.SetError(err)
if sub.opts.BatchErrorHandler != nil {
beh = sub.opts.BatchErrorHandler
}
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
if sub.handler != nil {
for _, p := range ms {
if err = sub.handler(p); err != nil {
p.SetError(err)
if sub.opts.ErrorHandler != nil {
eh = sub.opts.ErrorHandler
}
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)
}
}
}
}
}
}
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()
if !m.connected {
m.RUnlock()
return nil, errors.New("not connected")
return nil, ErrNotConnected
}
m.RUnlock()
options := NewSubscribeOptions(opts...)
id, err := uuid.NewRandom()
if err != nil {
return nil, err
}
options := NewSubscribeOptions(opts...)
sub := &memorySubscriber{
exit: make(chan bool, 1),
id: id.String(),
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()
id, err := uuid.NewRandom()
if err != nil {
return nil, err
}
options := NewSubscribeOptions(opts...)
sub := &memorySubscriber{
exit: make(chan bool, 1),
id: id.String(),
@@ -163,20 +286,20 @@ func (m *memoryBroker) Subscribe(ctx context.Context, topic string, handler Hand
}
m.Lock()
m.Subscribers[topic] = append(m.Subscribers[topic], sub)
m.subscribers[topic] = append(m.subscribers[topic], sub)
m.Unlock()
go func() {
<-sub.exit
m.Lock()
var newSubscribers []*memorySubscriber
for _, sb := range m.Subscribers[topic] {
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.subscribers[topic] = newSubscribers
m.Unlock()
}()
@@ -221,6 +344,10 @@ func (m *memoryEvent) Error() error {
return m.err
}
func (m *memoryEvent) SetError(err error) {
m.err = err
}
func (m *memorySubscriber) Options() SubscribeOptions {
return m.opts
}
@@ -238,6 +365,6 @@ func (m *memorySubscriber) Unsubscribe(ctx context.Context) error {
func NewBroker(opts ...Option) Broker {
return &memoryBroker{
opts: NewOptions(opts...),
Subscribers: make(map[string][]*memorySubscriber),
subscribers: make(map[string][]*memorySubscriber),
}
}

View File

@@ -4,8 +4,55 @@ import (
"context"
"fmt"
"testing"
"github.com/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, 0)
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) {
b := NewBroker()
ctx := context.Background()
@@ -26,20 +73,27 @@ func TestMemoryBroker(t *testing.T) {
t.Fatalf("Unexpected error subscribing %v", err)
}
msgs := make([]*Message, 0, 0)
for i := 0; i < count; i++ {
message := &Message{
Header: map[string]string{
"foo": "bar",
"id": fmt.Sprintf("%d", i),
metadata.HeaderTopic: topic,
"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 {
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 {
t.Fatalf("Unexpected error unsubscribing from %s: %v", topic, err)
}

View File

@@ -3,6 +3,7 @@ package broker
import (
"context"
"crypto/tls"
"time"
"github.com/unistack-org/micro/v3/codec"
"github.com/unistack-org/micro/v3/logger"
@@ -29,6 +30,8 @@ type Options struct {
TLSConfig *tls.Config
// ErrorHandler used when broker can't unmarshal incoming message
ErrorHandler Handler
// BatchErrorHandler used when broker can't unmashal incoming messages
BatchErrorHandler BatchHandler
// Name holds the broker name
Name string
// Addrs holds the broker address
@@ -71,11 +74,9 @@ func NewPublishOptions(opts ...PublishOption) PublishOptions {
options := PublishOptions{
Context: context.Background(),
}
for _, o := range opts {
o(&options)
}
return options
}
@@ -85,12 +86,18 @@ type SubscribeOptions struct {
Context context.Context
// ErrorHandler used when broker can't unmarshal incoming message
ErrorHandler Handler
// BatchErrorHandler used when broker can't unmashal incoming messages
BatchErrorHandler BatchHandler
// Group holds consumer group
Group string
// AutoAck flag specifies auto ack of incoming message when no error happens
AutoAck bool
// BodyOnly flag specifies that message contains only body bytes without header
BodyOnly bool
// BatchSize flag specifies max batch size
BatchSize int
// BatchWait flag specifies max wait time for batch filling
BatchWait time.Duration
}
// 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
func Addrs(addrs ...string) Option {
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
// in normal way, for example Codec errors
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
// in normal way, for example Codec errors
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
// Deprecated
func Queue(name string) SubscribeOption {
@@ -246,3 +230,55 @@ func SubscribeContext(ctx context.Context) SubscribeOption {
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

@@ -40,6 +40,7 @@ type Client interface {
Call(ctx context.Context, req Request, rsp interface{}, opts ...CallOption) error
Stream(ctx context.Context, req Request, opts ...CallOption) (Stream, error)
Publish(ctx context.Context, msg Message, opts ...PublishOption) error
BatchPublish(ctx context.Context, msg []Message, opts ...PublishOption) error
String() string
}

View File

@@ -9,16 +9,10 @@ import (
"github.com/unistack-org/micro/v3/metadata"
)
var (
// DefaultCodecs will be used to encode/decode data
DefaultCodecs = map[string]codec.Codec{
//"application/json": cjson.NewCodec,
//"application/json-rpc": cjsonrpc.NewCodec,
//"application/protobuf": cproto.NewCodec,
//"application/proto-rpc": cprotorpc.NewCodec,
"application/octet-stream": codec.NewCodec(),
}
)
// DefaultCodecs will be used to encode/decode data
var DefaultCodecs = map[string]codec.Codec{
"application/octet-stream": codec.NewCodec(),
}
type noopClient struct {
opts Options
@@ -179,7 +173,7 @@ func (n *noopClient) NewRequest(service, endpoint string, req interface{}, opts
}
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}
}
@@ -187,45 +181,60 @@ func (n *noopClient) Stream(ctx context.Context, req Request, opts ...CallOption
return &noopStream{}, nil
}
func (n *noopClient) Publish(ctx context.Context, p Message, opts ...PublishOption) error {
var body []byte
func (n *noopClient) BatchPublish(ctx context.Context, ps []Message, opts ...PublishOption) error {
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...)
md, ok := metadata.FromOutgoingContext(ctx)
if !ok {
md = metadata.New(0)
}
md["Content-Type"] = p.ContentType()
md["Micro-Topic"] = p.Topic()
msgs := make([]*broker.Message, 0, len(ps))
// 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())
for _, p := range ps {
md, ok := metadata.FromOutgoingContext(ctx)
if !ok {
md = metadata.New(0)
}
md[metadata.HeaderContentType] = p.ContentType()
topic := p.Topic()
// get the exchange
if len(options.Exchange) > 0 {
topic = options.Exchange
}
// set the body
b, err := cf.Marshal(p.Payload())
if err != nil {
return errors.InternalServerError("go.micro.client", err.Error())
md[metadata.HeaderTopic] = topic
var body []byte
// 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()
// 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))
return n.opts.Broker.BatchPublish(ctx, msgs,
broker.PublishContext(options.Context),
broker.PublishBodyOnly(options.BodyOnly),
)
}

View File

@@ -2,6 +2,7 @@ package client
import (
"context"
"crypto/tls"
"time"
"github.com/unistack-org/micro/v3/broker"
@@ -18,8 +19,8 @@ import (
// Options holds client options
type Options struct {
// CallOptions contains default CallOptions
CallOptions CallOptions
// Selector used to select needed address
Selector selector.Selector
// Logger used to log messages
Logger logger.Logger
// Tracer used for tracing
@@ -30,24 +31,26 @@ type Options struct {
Meter meter.Meter
// Router used to get route
Router router.Router
// Selector used to select needed address
Selector selector.Selector
// Transport used for transfer messages
Transport transport.Transport
// Context is used for external options
Context context.Context
// Codecs map
Codecs map[string]codec.Codec
// Lookup func used to get destination addr
Lookup LookupFunc
// Codecs map
Codecs map[string]codec.Codec
// TLSConfig specifies tls.Config for secure connection
TLSConfig *tls.Config
// Proxy is used for proxy requests
Proxy string
// ContentType is Used to select codec
// ContentType is used to select codec
ContentType string
// Name is the client name
Name string
// Wrappers contains wrappers
Wrappers []Wrapper
// CallOptions contains default CallOptions
CallOptions CallOptions
// PoolSize connection pool size
PoolSize int
// PoolTTL connection pool ttl
@@ -77,6 +80,8 @@ type CallOptions struct {
Backoff BackoffFunc
// Network name
Network string
// Content-Type
ContentType string
// CallWrappers call wrappers
CallWrappers []CallWrapper
// SelectOptions selector options
@@ -91,8 +96,8 @@ type CallOptions struct {
RequestTimeout time.Duration
// DialTimeout dial timeout
DialTimeout time.Duration
// AuthToken flag
AuthToken bool
// AuthToken string
AuthToken string
}
// Context pass context to client
@@ -113,6 +118,8 @@ func NewPublishOptions(opts ...PublishOption) PublishOptions {
// PublishOptions holds publish options
type PublishOptions struct {
// BodyOnly will publish only message body
BodyOnly bool
// Context used for external options
Context context.Context
// Exchange topic exchange name
@@ -167,14 +174,16 @@ func NewOptions(opts ...Option) Options {
RequestTimeout: DefaultRequestTimeout,
DialTimeout: transport.DefaultDialTimeout,
},
Lookup: LookupRoute,
PoolSize: DefaultPoolSize,
PoolTTL: DefaultPoolTTL,
Selector: random.NewSelector(),
Logger: logger.DefaultLogger,
Broker: broker.DefaultBroker,
Meter: meter.DefaultMeter,
Tracer: tracer.DefaultTracer,
Lookup: LookupRoute,
PoolSize: DefaultPoolSize,
PoolTTL: DefaultPoolTTL,
Selector: random.NewSelector(),
Logger: logger.DefaultLogger,
Broker: broker.DefaultBroker,
Meter: meter.DefaultMeter,
Tracer: tracer.DefaultTracer,
Router: router.DefaultRouter,
Transport: transport.DefaultTransport,
}
for _, o := range opts {
@@ -312,6 +321,22 @@ func Lookup(l LookupFunc) Option {
}
}
// TLSConfig specifies a *tls.Config
func TLSConfig(t *tls.Config) Option {
return func(o *Options) {
// set the internal tls
o.TLSConfig = t
// set the default transport if one is not
// already set. Required for Init call below.
// set the transport tls
o.Transport.Init(
transport.TLSConfig(t),
)
}
}
// Retries sets the retry count when making the request.
func Retries(i int) Option {
return func(o *Options) {
@@ -348,12 +373,35 @@ func DialTimeout(d time.Duration) Option {
}
// WithExchange sets the exchange to route a message through
// Deprecated
func WithExchange(e string) PublishOption {
return func(o *PublishOptions) {
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
// DERECATED
func WithBodyOnly(b bool) PublishOption {
return func(o *PublishOptions) {
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
func PublishContext(ctx context.Context) PublishOption {
return func(o *PublishOptions) {
@@ -361,6 +409,13 @@ func PublishContext(ctx context.Context) PublishOption {
}
}
// WithContentType specifies call content type
func WithContentType(ct string) CallOption {
return func(o *CallOptions) {
o.ContentType = ct
}
}
// WithAddress sets the remote addresses to use rather than using service discovery
func WithAddress(a ...string) CallOption {
return func(o *CallOptions) {
@@ -424,9 +479,9 @@ func WithDialTimeout(d time.Duration) CallOption {
// WithAuthToken is a CallOption which overrides the
// authorization header with the services own auth token
func WithAuthToken() CallOption {
func WithAuthToken(t string) CallOption {
return func(o *CallOptions) {
o.AuthToken = true
o.AuthToken = t
}
}
@@ -459,22 +514,30 @@ func WithSelectOptions(sops ...selector.SelectOption) CallOption {
}
// WithMessageContentType sets the message content type
// Deprecated
func WithMessageContentType(ct string) MessageOption {
return func(o *MessageOptions) {
o.ContentType = ct
}
}
// WithContentType specifies request content type
func WithContentType(ct string) RequestOption {
return func(o *RequestOptions) {
// MessageContentType sets the message content type
func MessageContentType(ct string) MessageOption {
return func(o *MessageOptions) {
o.ContentType = ct
}
}
// StreamingRequest specifies that request is streaming
func StreamingRequest() RequestOption {
func StreamingRequest(b bool) RequestOption {
return func(o *RequestOptions) {
o.Stream = true
o.Stream = b
}
}
// RequestContentType specifies request content type
func RequestContentType(ct string) RequestOption {
return func(o *RequestOptions) {
o.ContentType = ct
}
}

View File

@@ -5,13 +5,13 @@ import (
)
type testRequest struct {
opts RequestOptions
codec codec.Codec
body interface{}
service string
method string
endpoint string
contentType string
service string
opts RequestOptions
}
func (r *testRequest) ContentType() string {

View File

@@ -28,6 +28,8 @@ var (
DefaultMaxMsgSize int = 1024 * 1024 * 4 // 4Mb
// DefaultCodec is the global default codec
DefaultCodec Codec = NewCodec()
// DefaultTagName specifies struct tag name to control codec Marshal/Unmarshal
DefaultTagName = "codec"
)
// MessageType specifies message type for codec

View File

@@ -5,8 +5,7 @@ import (
"io"
)
type noopCodec struct {
}
type noopCodec struct{}
func (c *noopCodec) ReadHeader(conn io.Reader, m *Message, t MessageType) error {
return nil

View File

@@ -4,12 +4,17 @@ package config
import (
"context"
"errors"
"time"
)
var (
// DefaultConfig default config
DefaultConfig Config = NewConfig()
)
// DefaultConfig default config
var DefaultConfig Config = NewConfig()
// DefaultWatcherMinInterval default min interval for poll changes
var DefaultWatcherMinInterval = 5 * time.Second
// DefaultWatcherMinInterval default max interval for poll changes
var DefaultWatcherMaxInterval = 9 * time.Second
var (
// ErrCodecMissing is returned when codec needed and not specified
@@ -22,34 +27,38 @@ var (
// Config is an interface abstraction for dynamic configuration
type Config interface {
// Name returns name of config
Name() string
// Init the config
Init(opts ...Option) error
// Options in the config
Options() Options
// Load config from sources
Load(context.Context) error
Load(context.Context, ...LoadOption) error
// Save config to sources
Save(context.Context) error
// Watch a value for changes
// Watch(interface{}) (Watcher, error)
Save(context.Context, ...SaveOption) error
// Watch a config for changes
Watch(context.Context, ...WatchOption) (Watcher, error)
// String returns config type name
String() string
}
// Watcher is the config watcher
//type Watcher interface {
// Next() (, error)
// Stop() error
//}
type Watcher interface {
// Next blocks until update happens or error returned
Next() (map[string]interface{}, error)
// Stop stops watcher
Stop() error
}
// Load loads config from config sources
func Load(ctx context.Context, cs ...Config) error {
func Load(ctx context.Context, cs []Config, opts ...LoadOption) error {
var err error
for _, c := range cs {
if err = c.Init(); err != nil {
return err
}
if err = c.Load(ctx); err != nil {
if err = c.Load(ctx, opts...); err != nil {
return err
}
}

View File

@@ -7,6 +7,7 @@ import (
"strings"
"github.com/imdario/mergo"
"github.com/unistack-org/micro/v3/util/jitter"
rutil "github.com/unistack-org/micro/v3/util/reflect"
)
@@ -25,18 +26,31 @@ func (c *defaultConfig) Init(opts ...Option) error {
return nil
}
func (c *defaultConfig) Load(ctx context.Context) error {
func (c *defaultConfig) Load(ctx context.Context, opts ...LoadOption) error {
for _, fn := range c.opts.BeforeLoad {
if err := fn(ctx, c); err != nil && !c.opts.AllowFail {
return err
}
}
src, err := rutil.Zero(c.opts.Struct)
options := NewLoadOptions(opts...)
mopts := []func(*mergo.Config){mergo.WithTypeCheck}
if options.Override {
mopts = append(mopts, mergo.WithOverride)
}
if options.Append {
mopts = append(mopts, mergo.WithAppendSlice)
}
dst := c.opts.Struct
if options.Struct != nil {
dst = options.Struct
}
src, err := rutil.Zero(dst)
if err == nil {
valueOf := reflect.ValueOf(src)
if err = c.fillValues(ctx, valueOf); err == nil {
err = mergo.Merge(c.opts.Struct, src, mergo.WithOverride, mergo.WithTypeCheck, mergo.WithAppendSlice)
if err = fillValues(reflect.ValueOf(src), c.opts.StructTag); err == nil {
err = mergo.Merge(dst, src, mopts...)
}
}
@@ -54,7 +68,7 @@ func (c *defaultConfig) Load(ctx context.Context) error {
}
//nolint:gocyclo
func (c *defaultConfig) fillValue(ctx context.Context, value reflect.Value, val string) error {
func fillValue(value reflect.Value, val string) error {
if !rutil.IsEmpty(value) {
return nil
}
@@ -71,10 +85,10 @@ func (c *defaultConfig) fillValue(ctx context.Context, value reflect.Value, val
kv := strings.FieldsFunc(nval, func(c rune) bool { return c == '=' })
mkey := reflect.Indirect(reflect.New(kt))
mval := reflect.Indirect(reflect.New(et))
if err := c.fillValue(ctx, mkey, kv[0]); err != nil {
if err := fillValue(mkey, kv[0]); err != nil {
return err
}
if err := c.fillValue(ctx, mval, kv[1]); err != nil {
if err := fillValue(mval, kv[1]); err != nil {
return err
}
value.SetMapIndex(mkey, mval)
@@ -84,7 +98,7 @@ func (c *defaultConfig) fillValue(ctx context.Context, value reflect.Value, val
value.Set(reflect.MakeSlice(reflect.SliceOf(value.Type().Elem()), len(nvals), len(nvals)))
for idx, nval := range nvals {
nvalue := reflect.Indirect(reflect.New(value.Type().Elem()))
if err := c.fillValue(ctx, nvalue, nval); err != nil {
if err := fillValue(nvalue, nval); err != nil {
return err
}
value.Index(idx).Set(nvalue)
@@ -173,7 +187,7 @@ func (c *defaultConfig) fillValue(ctx context.Context, value reflect.Value, val
return nil
}
func (c *defaultConfig) fillValues(ctx context.Context, valueOf reflect.Value) error {
func fillValues(valueOf reflect.Value, tname string) error {
var values reflect.Value
if valueOf.Kind() == reflect.Ptr {
@@ -200,7 +214,7 @@ func (c *defaultConfig) fillValues(ctx context.Context, valueOf reflect.Value) e
switch value.Kind() {
case reflect.Struct:
value.Set(reflect.Indirect(reflect.New(value.Type())))
if err := c.fillValues(ctx, value); err != nil {
if err := fillValues(value, tname); err != nil {
return err
}
continue
@@ -214,17 +228,17 @@ func (c *defaultConfig) fillValues(ctx context.Context, valueOf reflect.Value) e
value.Set(reflect.New(value.Type().Elem()))
}
value = value.Elem()
if err := c.fillValues(ctx, value); err != nil {
if err := fillValues(value, tname); err != nil {
return err
}
continue
}
tag, ok := field.Tag.Lookup(c.opts.StructTag)
tag, ok := field.Tag.Lookup(tname)
if !ok {
continue
}
if err := c.fillValue(ctx, value, tag); err != nil {
if err := fillValue(value, tag); err != nil {
return err
}
}
@@ -232,7 +246,7 @@ func (c *defaultConfig) fillValues(ctx context.Context, valueOf reflect.Value) e
return nil
}
func (c *defaultConfig) Save(ctx context.Context) error {
func (c *defaultConfig) Save(ctx context.Context, opts ...SaveOption) error {
for _, fn := range c.opts.BeforeSave {
if err := fn(ctx, c); err != nil && !c.opts.AllowFail {
return err
@@ -256,6 +270,20 @@ func (c *defaultConfig) Name() string {
return c.opts.Name
}
func (c *defaultConfig) Watch(ctx context.Context, opts ...WatchOption) (Watcher, error) {
w := &defaultWatcher{
opts: c.opts,
wopts: NewWatchOptions(opts...),
done: make(chan struct{}),
vchan: make(chan map[string]interface{}),
echan: make(chan error),
}
go w.run()
return w, nil
}
// NewConfig returns new default config source
func NewConfig(opts ...Option) Config {
options := NewOptions(opts...)
@@ -264,3 +292,76 @@ func NewConfig(opts ...Option) Config {
}
return &defaultConfig{opts: options}
}
type defaultWatcher struct {
opts Options
wopts WatchOptions
done chan struct{}
vchan chan map[string]interface{}
echan chan error
}
func (w *defaultWatcher) run() {
ticker := jitter.NewTicker(w.wopts.MinInterval, w.wopts.MaxInterval)
defer ticker.Stop()
src := w.opts.Struct
if w.wopts.Struct != nil {
src = w.wopts.Struct
}
for {
select {
case <-w.done:
return
case <-ticker.C:
dst, err := rutil.Zero(src)
if err == nil {
err = fillValues(reflect.ValueOf(dst), w.opts.StructTag)
}
if err != nil {
w.echan <- err
return
}
srcmp, err := rutil.StructFieldsMap(src)
if err != nil {
w.echan <- err
return
}
dstmp, err := rutil.StructFieldsMap(dst)
if err != nil {
w.echan <- err
return
}
for sk, sv := range srcmp {
if reflect.DeepEqual(dstmp[sk], sv) {
delete(dstmp, sk)
}
}
if len(dstmp) > 0 {
w.vchan <- dstmp
src = dst
}
}
}
}
func (w *defaultWatcher) Next() (map[string]interface{}, error) {
select {
case <-w.done:
break
case err := <-w.echan:
return nil, err
case v, ok := <-w.vchan:
if !ok {
break
}
return v, nil
}
return nil, ErrWatcherStopped
}
func (w *defaultWatcher) Stop() error {
close(w.done)
return nil
}

View File

@@ -4,36 +4,88 @@ import (
"context"
"fmt"
"testing"
"time"
"github.com/unistack-org/micro/v3/config"
)
type Cfg struct {
StringValue string `default:"string_value"`
IntValue int `default:"99"`
IgnoreValue string `json:"-"`
StructValue struct {
StringValue string `default:"string_value"`
}
IntValue int `default:"99"`
}
func TestWatch(t *testing.T) {
ctx := context.Background()
conf := &Cfg{IntValue: 10}
cfg := config.NewConfig(config.Struct(conf))
if err := cfg.Init(); err != nil {
t.Fatal(err)
}
if err := cfg.Load(ctx); err != nil {
t.Fatal(err)
}
w, err := cfg.Watch(ctx, config.WatchInterval(200*time.Millisecond, 500*time.Millisecond))
if err != nil {
t.Fatal(err)
}
defer func() {
_ = w.Stop()
}()
done := make(chan struct{})
go func() {
for {
mp, err := w.Next()
if err != nil && err != config.ErrWatcherStopped {
t.Fatal(err)
} else if err == config.ErrWatcherStopped {
return
}
if len(mp) != 1 {
t.Fatal(fmt.Errorf("default watcher err: %v", mp))
}
v, ok := mp["IntValue"]
if !ok {
t.Fatal(fmt.Errorf("default watcher err: %v", v))
}
if nv, ok := v.(int); !ok || nv != 99 {
t.Fatal(fmt.Errorf("default watcher err: %v", v))
}
close(done)
return
}
}()
<-done
}
func TestDefault(t *testing.T) {
ctx := context.Background()
conf := &Cfg{IntValue: 10}
blfn := func(ctx context.Context, cfg config.Config) error {
conf, ok := cfg.Options().Struct.(*Cfg)
nconf, ok := cfg.Options().Struct.(*Cfg)
if !ok {
return fmt.Errorf("failed to get Struct from options: %v", cfg.Options())
}
conf.StringValue = "before_load"
nconf.StringValue = "before_load"
return nil
}
alfn := func(ctx context.Context, cfg config.Config) error {
conf, ok := cfg.Options().Struct.(*Cfg)
nconf, ok := cfg.Options().Struct.(*Cfg)
if !ok {
return fmt.Errorf("failed to get Struct from options: %v", cfg.Options())
}
conf.StringValue = "after_load"
nconf.StringValue = "after_load"
return nil
}
@@ -47,6 +99,6 @@ func TestDefault(t *testing.T) {
if conf.StringValue != "after_load" {
t.Fatal("AfterLoad option not working")
}
t.Logf("%#+v\n", conf)
_ = conf
//t.Logf("%#+v\n", conf)
}

View File

@@ -2,6 +2,7 @@ package config
import (
"context"
"time"
"github.com/unistack-org/micro/v3/codec"
"github.com/unistack-org/micro/v3/logger"
@@ -57,6 +58,69 @@ func NewOptions(opts ...Option) Options {
return options
}
// LoadOption function signature
type LoadOption func(o *LoadOptions)
// LoadOptions struct
type LoadOptions struct {
Struct interface{}
Override bool
Append bool
}
func NewLoadOptions(opts ...LoadOption) LoadOptions {
options := LoadOptions{}
for _, o := range opts {
o(&options)
}
return options
}
// LoadOverride override values when load
func LoadOverride(b bool) LoadOption {
return func(o *LoadOptions) {
o.Override = b
}
}
// LoadAppend override values when load
func LoadAppend(b bool) LoadOption {
return func(o *LoadOptions) {
o.Append = b
}
}
// LoadStruct override struct for loading
func LoadStruct(src interface{}) LoadOption {
return func(o *LoadOptions) {
o.Struct = src
}
}
// SaveOption function signature
type SaveOption func(o *SaveOptions)
// 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 {
options := SaveOptions{}
for _, o := range opts {
o(&options)
}
return options
}
// AllowFail allows config source to fail
func AllowFail(b bool) Option {
return func(o *Options) {
@@ -140,3 +204,60 @@ func Name(n string) Option {
o.Name = n
}
}
// WatchOptions struuct
type WatchOptions struct {
// Context used by non default options
Context context.Context
// Coalesce multiple events to one
Coalesce bool
// 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
// Struct for filling
Struct interface{}
}
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 {
Value string
SubConfig *SubConfig
Config *Config
}
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

@@ -17,7 +17,6 @@ func TestFromError(t *testing.T) {
if merr.Id != "go.micro.test" || merr.Code != 404 {
t.Fatalf("invalid conversation %v != %v", err, merr)
}
}
func TestEqual(t *testing.T) {
@@ -32,7 +31,6 @@ func TestEqual(t *testing.T) {
if Equal(err1, err3) {
t.Fatal("errors must be not equal")
}
}
func TestErrors(t *testing.T) {

34
flow/context.go Normal file
View File

@@ -0,0 +1,34 @@
package flow
import (
"context"
)
type flowKey struct{}
// FromContext returns Flow from context
func FromContext(ctx context.Context) (Flow, bool) {
if ctx == nil {
return nil, false
}
c, ok := ctx.Value(flowKey{}).(Flow)
return c, ok
}
// NewContext stores Flow to context
func NewContext(ctx context.Context, f Flow) context.Context {
if ctx == nil {
ctx = context.Background()
}
return context.WithValue(ctx, flowKey{}, f)
}
// 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

@@ -44,10 +44,10 @@ func TestDag(t *testing.T) {
var steps [][]string
fn := func(n dag.Vertex, idx int) error {
if idx == 0 {
steps = make([][]string, 1, 1)
steps = make([][]string, 1)
steps[0] = make([]string, 0, 1)
} else if idx >= len(steps) {
tsteps := make([][]string, idx+1, idx+1)
tsteps := make([][]string, idx+1)
copy(tsteps, steps)
steps = tsteps
steps[idx] = make([]string, 0, 1)

588
flow/default.go Normal file
View File

@@ -0,0 +1,588 @@
package flow
import (
"context"
"fmt"
"path/filepath"
"sync"
"github.com/google/uuid"
"github.com/silas/dag"
"github.com/unistack-org/micro/v3/client"
"github.com/unistack-org/micro/v3/codec"
"github.com/unistack-org/micro/v3/logger"
"github.com/unistack-org/micro/v3/metadata"
"github.com/unistack-org/micro/v3/store"
)
type microFlow struct {
opts Options
}
type microWorkflow struct {
id string
g *dag.AcyclicGraph
init bool
sync.RWMutex
opts Options
steps map[string]Step
status Status
}
func (w *microWorkflow) ID() string {
return w.id
}
func (w *microWorkflow) Steps() ([][]Step, error) {
return w.getSteps("", false)
}
func (w *microWorkflow) Status() Status {
return w.status
}
func (w *microWorkflow) AppendSteps(steps ...Step) error {
w.Lock()
for _, s := range steps {
w.steps[s.String()] = s
w.g.Add(s)
}
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) 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)
}
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 root dag.Vertex
var err error
fn := func(n dag.Vertex, idx int) error {
if idx == 0 {
steps = make([][]Step, 1)
steps[0] = make([]Step, 0, 1)
} else if idx >= len(steps) {
tsteps := make([][]Step, idx+1)
copy(tsteps, steps)
steps = tsteps
steps[idx] = make([]Step, 0, 1)
}
steps[idx] = append(steps[idx], n.(Step))
return nil
}
if start != "" {
var ok bool
w.RLock()
root, ok = w.steps[start]
w.RUnlock()
if !ok {
return nil, ErrStepNotExists
}
} else {
root, err = w.g.Root()
if err != nil {
return nil, err
}
}
if reverse {
err = w.g.SortedReverseDepthFirstWalk([]dag.Vertex{root}, fn)
} else {
err = w.g.SortedDepthFirstWalk([]dag.Vertex{root}, fn)
}
if err != nil {
return nil, err
}
return steps, nil
}
func (w *microWorkflow) Abort(ctx context.Context, eid string) error {
workflowStore := store.NewNamespaceStore(w.opts.Store, filepath.Join("workflows", eid))
return workflowStore.Write(ctx, "status", &codec.Frame{Data: []byte(StatusAborted.String())})
}
func (w *microWorkflow) Suspend(ctx context.Context, eid string) error {
workflowStore := store.NewNamespaceStore(w.opts.Store, filepath.Join("workflows", eid))
return workflowStore.Write(ctx, "status", &codec.Frame{Data: []byte(StatusSuspend.String())})
}
func (w *microWorkflow) Resume(ctx context.Context, eid string) error {
workflowStore := store.NewNamespaceStore(w.opts.Store, filepath.Join("workflows", eid))
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()
uid, err := uuid.NewRandom()
if err != nil {
return "", err
}
eid := uid.String()
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
}
var wg sync.WaitGroup
cherr := make(chan error, 1)
chstatus := make(chan Status, 1)
nctx, cancel := context.WithCancel(ctx)
defer cancel()
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),
)
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() {
for idx := range steps {
for nidx := range steps[idx] {
wStatus := &codec.Frame{}
if werr := workflowStore.Read(w.opts.Context, "status", wStatus); werr != nil {
cherr <- werr
return
}
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]
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
} else {
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
}
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
} else {
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
}
}
}
}
}
close(done)
}()
if options.Async {
return eid, nil
}
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 uid.String(), 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)
}
break
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)
}
break
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)
}
break
}
return uid.String(), err
}
func NewFlow(opts ...Option) Flow {
options := NewOptions(opts...)
return &microFlow{opts: options}
}
func (f *microFlow) Options() Options {
return f.opts
}
func (f *microFlow) Init(opts ...Option) error {
for _, o := range opts {
o(&f.opts)
}
if err := f.opts.Client.Init(); err != nil {
return err
}
if err := f.opts.Tracer.Init(); err != nil {
return err
}
if err := f.opts.Logger.Init(); err != nil {
return err
}
if err := f.opts.Meter.Init(); err != nil {
return err
}
if err := f.opts.Store.Init(); err != nil {
return err
}
return nil
}
func (f *microFlow) WorkflowList(ctx context.Context) ([]Workflow, error) {
return nil, nil
}
func (f *microFlow) WorkflowCreate(ctx context.Context, id string, steps ...Step) (Workflow, error) {
w := &microWorkflow{opts: f.opts, id: id, g: &dag.AcyclicGraph{}, steps: make(map[string]Step, len(steps))}
for _, s := range steps {
w.steps[s.String()] = s
w.g.Add(s)
}
for _, dst := range steps {
for _, req := range dst.Requires() {
src, ok := w.steps[req]
if !ok {
return nil, ErrStepNotExists
}
w.g.Connect(dag.BasicEdge(src, dst))
}
}
if err := w.g.Validate(); err != nil {
return nil, err
}
w.g.TransitiveReduction()
w.init = true
return w, nil
}
func (f *microFlow) WorkflowRemove(ctx context.Context, id string) error {
return nil
}
func (f *microFlow) WorkflowSave(ctx context.Context, w Workflow) error {
return nil
}
func (f *microFlow) WorkflowLoad(ctx context.Context, id string) (Workflow, error) {
return nil, nil
}
type microCallStep struct {
opts StepOptions
service string
method string
rsp *Message
req *Message
status Status
}
func (s *microCallStep) Request() *Message {
return s.req
}
func (s *microCallStep) Response() *Message {
return s.rsp
}
func (s *microCallStep) ID() string {
return s.String()
}
func (s *microCallStep) Options() StepOptions {
return s.opts
}
func (s *microCallStep) Endpoint() string {
return s.method
}
func (s *microCallStep) Requires() []string {
return s.opts.Requires
}
func (s *microCallStep) Require(steps ...Step) error {
for _, step := range steps {
s.opts.Requires = append(s.opts.Requires, step.String())
}
return nil
}
func (s *microCallStep) String() string {
if s.opts.ID != "" {
return s.opts.ID
}
return fmt.Sprintf("%s.%s", s.service, s.method)
}
func (s *microCallStep) Name() string {
return s.String()
}
func (s *microCallStep) Hashcode() interface{} {
return s.String()
}
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...)
if options.Client == nil {
return nil, ErrMissingClient
}
rsp := &codec.Frame{}
copts := []client.CallOption{client.WithRetries(0)}
if options.Timeout > 0 {
copts = append(copts, client.WithRequestTimeout(options.Timeout), client.WithDialTimeout(options.Timeout))
}
nctx := metadata.NewOutgoingContext(ctx, req.Header)
err := options.Client.Call(nctx, options.Client.NewRequest(s.service, s.method, &codec.Frame{Data: req.Body}), rsp)
if err != nil {
return nil, err
}
md, _ := metadata.FromOutgoingContext(nctx)
return &Message{Header: md, Body: rsp.Data}, err
}
type microPublishStep struct {
opts StepOptions
topic string
req *Message
rsp *Message
status Status
}
func (s *microPublishStep) Request() *Message {
return s.req
}
func (s *microPublishStep) Response() *Message {
return s.rsp
}
func (s *microPublishStep) ID() string {
return s.String()
}
func (s *microPublishStep) Options() StepOptions {
return s.opts
}
func (s *microPublishStep) Endpoint() string {
return s.topic
}
func (s *microPublishStep) Requires() []string {
return s.opts.Requires
}
func (s *microPublishStep) Require(steps ...Step) error {
for _, step := range steps {
s.opts.Requires = append(s.opts.Requires, step.String())
}
return nil
}
func (s *microPublishStep) String() string {
if s.opts.ID != "" {
return s.opts.ID
}
return fmt.Sprintf("%s", s.topic)
}
func (s *microPublishStep) Name() string {
return s.String()
}
func (s *microPublishStep) Hashcode() interface{} {
return s.String()
}
func (s *microPublishStep) GetStatus() Status {
return s.status
}
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...)
return &microCallStep{service: service, method: name + "." + method, opts: options}
}
func NewPublishStep(topic string, opts ...StepOption) Step {
options := NewStepOptions(opts...)
return &microPublishStep{topic: topic, opts: options}
}

View File

@@ -1,7 +1,152 @@
// Package flow is an interface used for saga pattern messaging
// Package flow is an interface used for saga pattern microservice workflow
package flow
type Step interface {
// Endpoint returns service_name.service_method
Endpoint() string
import (
"context"
"errors"
"sync"
"sync/atomic"
"github.com/unistack-org/micro/v3/metadata"
)
var (
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
type Step interface {
// ID returns step id
ID() string
// Endpoint returns rpc endpoint service_name.service_method or broker topic
Endpoint() string
// Execute step run
Execute(ctx context.Context, req *Message, opts ...ExecuteOption) (*Message, error)
// Requires returns dependent steps
Requires() []string
// Options returns step options
Options() StepOptions
// Require add required steps
Require(steps ...Step) error
// 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
type Workflow interface {
// ID returns id of the workflow
ID() string
// Execute workflow with args, return execution id and error
Execute(ctx context.Context, req *Message, opts ...ExecuteOption) (string, error)
// RemoveSteps remove steps from workflow
RemoveSteps(steps ...Step) error
// AppendSteps append steps to workflow
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, eid string) error
// Resume resumes execution
Resume(ctx context.Context, eid string) error
// Abort abort execution
Abort(ctx context.Context, eid string) error
}
// Flow the base interface to interact with workflows
type Flow interface {
// Options returns options
Options() Options
// Init initialize
Init(...Option) error
// WorkflowCreate creates new workflow with specific id and steps
WorkflowCreate(ctx context.Context, id string, steps ...Step) (Workflow, error)
// WorkflowSave saves workflow
WorkflowSave(ctx context.Context, w Workflow) error
// WorkflowLoad loads workflow with specific id
WorkflowLoad(ctx context.Context, id string) (Workflow, error)
// WorkflowList lists all workflows
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()
}

230
flow/options.go Normal file
View File

@@ -0,0 +1,230 @@
package flow
import (
"context"
"time"
"github.com/unistack-org/micro/v3/client"
"github.com/unistack-org/micro/v3/logger"
"github.com/unistack-org/micro/v3/meter"
"github.com/unistack-org/micro/v3/store"
"github.com/unistack-org/micro/v3/tracer"
)
// Option func
type Option func(*Options)
// Options server struct
type Options struct {
// Context holds the external options and can be used for flow shutdown
Context context.Context
// Client holds the client.Client
Client client.Client
// Tracer holds the tracer
Tracer tracer.Tracer
// Logger holds the logger
Logger logger.Logger
// Meter holds the meter
Meter meter.Meter
// Store used for intermediate results
Store store.Store
}
// NewOptions returns new options struct with default or passed values
func NewOptions(opts ...Option) Options {
options := Options{
Context: context.Background(),
Logger: logger.DefaultLogger,
Meter: meter.DefaultMeter,
Tracer: tracer.DefaultTracer,
Client: client.DefaultClient,
}
for _, o := range opts {
o(&options)
}
return options
}
// Logger sets the logger option
func Logger(l logger.Logger) Option {
return func(o *Options) {
o.Logger = l
}
}
// Meter sets the meter option
func Meter(m meter.Meter) Option {
return func(o *Options) {
o.Meter = m
}
}
// Client to use for sync/async communication
func Client(c client.Client) Option {
return func(o *Options) {
o.Client = c
}
}
// Context specifies a context for the service.
// Can be used to signal shutdown of the flow
// Can be used for extra option values.
func Context(ctx context.Context) Option {
return func(o *Options) {
o.Context = ctx
}
}
// Tracer mechanism for distributed tracking
func Tracer(t tracer.Tracer) Option {
return func(o *Options) {
o.Tracer = t
}
}
// Store used for intermediate results
func Store(s store.Store) Option {
return func(o *Options) {
o.Store = s
}
}
// WorflowOption signature
type WorkflowOption func(*WorkflowOptions)
// WorkflowOptions holds workflow options
type WorkflowOptions struct {
ID string
Context context.Context
}
// WorkflowID set workflow id
func WorkflowID(id string) WorkflowOption {
return func(o *WorkflowOptions) {
o.ID = id
}
}
type ExecuteOptions struct {
// Client holds the client.Client
Client client.Client
// Tracer holds the tracer
Tracer tracer.Tracer
// Logger holds the logger
Logger logger.Logger
// Meter holds the meter
Meter meter.Meter
// Context can be used to abort execution or pass additional opts
Context context.Context
// Start step
Start string
// Reverse execution
Reverse bool
// Timeout for execution
Timeout time.Duration
// Async enables async execution
Async bool
}
type ExecuteOption func(*ExecuteOptions)
func ExecuteClient(c client.Client) ExecuteOption {
return func(o *ExecuteOptions) {
o.Client = c
}
}
func ExecuteTracer(t tracer.Tracer) ExecuteOption {
return func(o *ExecuteOptions) {
o.Tracer = t
}
}
func ExecuteLogger(l logger.Logger) ExecuteOption {
return func(o *ExecuteOptions) {
o.Logger = l
}
}
func ExecuteMeter(m meter.Meter) ExecuteOption {
return func(o *ExecuteOptions) {
o.Meter = m
}
}
func ExecuteContext(ctx context.Context) ExecuteOption {
return func(o *ExecuteOptions) {
o.Context = ctx
}
}
func ExecuteReverse(b bool) ExecuteOption {
return func(o *ExecuteOptions) {
o.Reverse = b
}
}
func ExecuteTimeout(td time.Duration) ExecuteOption {
return func(o *ExecuteOptions) {
o.Timeout = td
}
}
func ExecuteAsync(b bool) ExecuteOption {
return func(o *ExecuteOptions) {
o.Async = b
}
}
func NewExecuteOptions(opts ...ExecuteOption) ExecuteOptions {
options := ExecuteOptions{
Client: client.DefaultClient,
Logger: logger.DefaultLogger,
Tracer: tracer.DefaultTracer,
Meter: meter.DefaultMeter,
Context: context.Background(),
}
for _, o := range opts {
o(&options)
}
return options
}
type StepOptions struct {
ID string
Context context.Context
Requires []string
Fallback string
}
type StepOption func(*StepOptions)
func NewStepOptions(opts ...StepOption) StepOptions {
options := StepOptions{
Context: context.Background(),
}
for _, o := range opts {
o(&options)
}
return options
}
func StepID(id string) StepOption {
return func(o *StepOptions) {
o.ID = id
}
}
func StepRequires(steps ...string) StepOption {
return func(o *StepOptions) {
o.Requires = steps
}
}
func StepFallback(step string) StepOption {
return func(o *StepOptions) {
o.Fallback = step
}
}

View File

@@ -1,3 +0,0 @@
package micro
//go:generate ./.github/generate.sh

6
go.mod
View File

@@ -3,11 +3,11 @@ module github.com/unistack-org/micro/v3
go 1.16
require (
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/ef-ds/deque v1.0.4
github.com/google/uuid v1.2.0
github.com/golang-jwt/jwt v3.2.2+incompatible
github.com/google/uuid v1.3.0
github.com/imdario/mergo v0.3.12
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/silas/dag v0.0.0-20210121180416-41cf55125c34
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4
golang.org/x/net v0.0.0-20210510120150-4163338589ed
)

16
go.sum
View File

@@ -1,21 +1,21 @@
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/go.mod h1:gXDnTC3yqvBcHbq2lcExjtAcVrOnJCbMcZXmuj8Z4tg=
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
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/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/silas/dag v0.0.0-20210121180416-41cf55125c34 h1:vBfVmA5mZhsQa2jr1FOL9nfA37N/jnbBmi5XUfviVTI=
github.com/silas/dag v0.0.0-20210121180416-41cf55125c34/go.mod h1:7RTUFBdIRC9nZ7/3RyRNH1bdqIShrDejd1YbLwgPS+I=
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4 h1:b0LrWgu8+q7z4J+0Y3Umo5q1dL7NXBkKBWkaVkAq17E=
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
golang.org/x/net v0.0.0-20210510120150-4163338589ed h1:p9UgmWI9wKpfYmgaV/IZKGdXc5qEK45tDwwwDyjS26I=
golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/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/text v0.3.3/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=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=

View File

@@ -21,9 +21,11 @@ func init() {
}
type defaultLogger struct {
opts Options
enc *json.Encoder
opts Options
sync.RWMutex
logFunc LogFunc
logfFunc LogfFunc
}
// Init(opts...) should only overwrite provided options
@@ -33,6 +35,12 @@ func (l *defaultLogger) Init(opts ...Option) error {
o(&l.opts)
}
l.enc = json.NewEncoder(l.opts.Out)
// wrap the Log func
for i := len(l.opts.Wrappers); i > 0; i-- {
l.logFunc = l.opts.Wrappers[i-1].Log(l.logFunc)
l.logfFunc = l.opts.Wrappers[i-1].Logf(l.logfFunc)
}
l.Unlock()
return nil
}
@@ -121,27 +129,27 @@ func (l *defaultLogger) Fatal(ctx context.Context, args ...interface{}) {
}
func (l *defaultLogger) Infof(ctx context.Context, msg string, args ...interface{}) {
l.Logf(ctx, InfoLevel, msg, args...)
l.logfFunc(ctx, InfoLevel, msg, args...)
}
func (l *defaultLogger) Errorf(ctx context.Context, msg string, args ...interface{}) {
l.Logf(ctx, ErrorLevel, msg, args...)
l.logfFunc(ctx, ErrorLevel, msg, args...)
}
func (l *defaultLogger) Debugf(ctx context.Context, msg string, args ...interface{}) {
l.Logf(ctx, DebugLevel, msg, args...)
l.logfFunc(ctx, DebugLevel, msg, args...)
}
func (l *defaultLogger) Warnf(ctx context.Context, msg string, args ...interface{}) {
l.Logf(ctx, WarnLevel, msg, args...)
l.logfFunc(ctx, WarnLevel, msg, args...)
}
func (l *defaultLogger) Tracef(ctx context.Context, msg string, args ...interface{}) {
l.Logf(ctx, TraceLevel, msg, args...)
l.logfFunc(ctx, TraceLevel, msg, args...)
}
func (l *defaultLogger) Fatalf(ctx context.Context, msg string, args ...interface{}) {
l.Logf(ctx, FatalLevel, msg, args...)
l.logfFunc(ctx, FatalLevel, msg, args...)
os.Exit(1)
}
@@ -207,7 +215,11 @@ func (l *defaultLogger) Options() Options {
// NewLogger builds a new logger based on options
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)
return l
}

View File

@@ -8,6 +8,8 @@ var (
DefaultLogger Logger = NewLogger()
// DefaultLevel used by logger
DefaultLevel Level = InfoLevel
// DefaultCallerSkipCount used by logger
DefaultCallerSkipCount = 2
)
// Logger is a generic logging interface

View File

@@ -1,13 +1,15 @@
package logger
import (
"bytes"
"context"
"testing"
)
func TestLogger(t *testing.T) {
ctx := context.TODO()
l := NewLogger(WithLevel(TraceLevel))
buf := bytes.NewBuffer(nil)
l := NewLogger(WithLevel(TraceLevel), WithOutput(buf))
if err := l.Init(); err != nil {
t.Fatal(err)
}
@@ -15,4 +17,52 @@ func TestLogger(t *testing.T) {
l.Warn(ctx, "warn_msg1")
l.Fields(map[string]interface{}{"error": "test"}).Info(ctx, "error message")
l.Warn(ctx, "first", " ", "second")
if !bytes.Contains(buf.Bytes(), []byte(`"level":"trace","msg":"trace_msg1"`)) {
t.Fatalf("logger error, buf %s", buf.Bytes())
}
if !bytes.Contains(buf.Bytes(), []byte(`"warn","msg":"warn_msg1"`)) {
t.Fatalf("logger error, buf %s", buf.Bytes())
}
if !bytes.Contains(buf.Bytes(), []byte(`"error":"test","level":"info","msg":"error message"`)) {
t.Fatalf("logger error, buf %s", buf.Bytes())
}
if !bytes.Contains(buf.Bytes(), []byte(`"level":"warn","msg":"first second"`)) {
t.Fatalf("logger error, buf %s", buf.Bytes())
}
}
func TestLoggerWrapper(t *testing.T) {
ctx := context.TODO()
buf := bytes.NewBuffer(nil)
l := NewLogger(WithLevel(TraceLevel), WithOutput(buf))
if err := l.Init(WrapLogger(NewOmitWrapper())); err != nil {
t.Fatal(err)
}
type secret struct {
Name string
Passw string `logger:"omit"`
}
s := &secret{Name: "name", Passw: "secret"}
l.Errorf(ctx, "test %#+v", s)
if !bytes.Contains(buf.Bytes(), []byte(`logger.secret{Name:\"name\", Passw:\"\"}"`)) {
t.Fatalf("omit not works, struct: %v, output: %s", s, buf.Bytes())
}
}
func TestOmitLoggerWrapper(t *testing.T) {
ctx := context.TODO()
buf := bytes.NewBuffer(nil)
l := NewOmitLogger(NewLogger(WithLevel(TraceLevel), WithOutput(buf)))
if err := l.Init(); err != nil {
t.Fatal(err)
}
type secret struct {
Name string
Passw string `logger:"omit"`
}
s := &secret{Name: "name", Passw: "secret"}
l.Errorf(ctx, "test %#+v", s)
if !bytes.Contains(buf.Bytes(), []byte(`logger.secret{Name:\"name\", Passw:\"\"}"`)) {
t.Fatalf("omit not works, struct: %v, output: %s", s, buf.Bytes())
}
}

View File

@@ -23,6 +23,8 @@ type Options struct {
CallerSkipCount int
// The logging level the logger should log
Level Level
// Wrappers logger wrapper that called before actual Log/Logf function
Wrappers []Wrapper
}
// NewOptions creates new options struct
@@ -31,7 +33,7 @@ func NewOptions(opts ...Option) Options {
Level: DefaultLevel,
Fields: make(map[string]interface{}),
Out: os.Stderr,
CallerSkipCount: 0,
CallerSkipCount: DefaultCallerSkipCount,
Context: context.Background(),
}
for _, o := range opts {
@@ -76,8 +78,15 @@ func WithContext(ctx context.Context) Option {
}
// WithName sets the name
func withName(n string) Option {
func WithName(n string) Option {
return func(o *Options) {
o.Name = n
}
}
// WrapLogger adds a logger Wrapper to a list of options passed into the logger
func WrapLogger(w Wrapper) Option {
return func(o *Options) {
o.Wrappers = append(o.Wrappers, w)
}
}

154
logger/wrapper.go Normal file
View File

@@ -0,0 +1,154 @@
package logger
import (
"context"
"reflect"
rutil "github.com/unistack-org/micro/v3/util/reflect"
)
// LogFunc function used for Log method
type LogFunc func(ctx context.Context, level Level, args ...interface{})
// LogfFunc function used for Logf method
type LogfFunc func(ctx context.Context, level Level, msg string, args ...interface{})
type Wrapper interface {
// Log logs message with needed level
Log(LogFunc) LogFunc
// Logf logs message with needed level
Logf(LogfFunc) LogfFunc
}
var (
_ Logger = &OmitLogger{}
)
type OmitLogger struct {
l Logger
}
func NewOmitLogger(l Logger) Logger {
return &OmitLogger{l: l}
}
func (w *OmitLogger) Init(opts ...Option) error {
return w.l.Init(append(opts, WrapLogger(NewOmitWrapper()))...)
}
func (w *OmitLogger) V(level Level) bool {
return w.l.V(level)
}
func (w *OmitLogger) Options() Options {
return w.l.Options()
}
func (w *OmitLogger) Fields(fields map[string]interface{}) Logger {
return w.l.Fields(fields)
}
func (w *OmitLogger) Info(ctx context.Context, args ...interface{}) {
w.l.Info(ctx, args...)
}
func (w *OmitLogger) Trace(ctx context.Context, args ...interface{}) {
w.l.Trace(ctx, args...)
}
func (w *OmitLogger) Debug(ctx context.Context, args ...interface{}) {
w.l.Debug(ctx, args...)
}
func (w *OmitLogger) Warn(ctx context.Context, args ...interface{}) {
w.l.Warn(ctx, args...)
}
func (w *OmitLogger) Error(ctx context.Context, args ...interface{}) {
w.l.Error(ctx, args...)
}
func (w *OmitLogger) Fatal(ctx context.Context, args ...interface{}) {
w.l.Fatal(ctx, args...)
}
func (w *OmitLogger) Infof(ctx context.Context, msg string, args ...interface{}) {
w.l.Infof(ctx, msg, args...)
}
func (w *OmitLogger) Tracef(ctx context.Context, msg string, args ...interface{}) {
w.l.Tracef(ctx, msg, args...)
}
func (w *OmitLogger) Debugf(ctx context.Context, msg string, args ...interface{}) {
w.l.Debugf(ctx, msg, args...)
}
func (w *OmitLogger) Warnf(ctx context.Context, msg string, args ...interface{}) {
w.l.Warnf(ctx, msg, args...)
}
func (w *OmitLogger) Errorf(ctx context.Context, msg string, args ...interface{}) {
w.l.Errorf(ctx, msg, args...)
}
func (w *OmitLogger) Fatalf(ctx context.Context, msg string, args ...interface{}) {
w.l.Fatalf(ctx, msg, args...)
}
func (w *OmitLogger) Log(ctx context.Context, level Level, args ...interface{}) {
w.l.Log(ctx, level, args...)
}
func (w *OmitLogger) Logf(ctx context.Context, level Level, msg string, args ...interface{}) {
w.l.Logf(ctx, level, msg, args...)
}
func (w *OmitLogger) String() string {
return w.l.String()
}
type OmitWrapper struct{}
func NewOmitWrapper() Wrapper {
return &OmitWrapper{}
}
func getArgs(args []interface{}) []interface{} {
nargs := make([]interface{}, 0, len(args))
var err error
for _, arg := range args {
val := reflect.ValueOf(arg)
switch val.Kind() {
case reflect.Ptr:
val = val.Elem()
}
narg := arg
if val.Kind() == reflect.Struct {
if narg, err = rutil.Zero(arg); err == nil {
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)
}
return nargs
}
func (w *OmitWrapper) Log(fn LogFunc) LogFunc {
return func(ctx context.Context, level Level, args ...interface{}) {
fn(ctx, level, getArgs(args)...)
}
}
func (w *OmitWrapper) Logf(fn LogfFunc) LogfFunc {
return func(ctx context.Context, level Level, msg string, args ...interface{}) {
fn(ctx, level, msg, getArgs(args)...)
}
}

View File

@@ -3,6 +3,7 @@ package wrapper
import (
"context"
"fmt"
"github.com/unistack-org/micro/v3/client"
"github.com/unistack-org/micro/v3/logger"
@@ -57,6 +58,8 @@ var (
}
return labels
}
DefaultSkipEndpoints = []string{"Meter.Metrics"}
)
type lWrapper struct {
@@ -67,21 +70,23 @@ type lWrapper struct {
opts Options
}
type ClientCallObserver func(context.Context, client.Request, interface{}, []client.CallOption, error) []string
type ClientStreamObserver func(context.Context, client.Request, []client.CallOption, client.Stream, error) []string
type ClientPublishObserver func(context.Context, client.Message, []client.PublishOption, error) []string
type ClientCallFuncObserver func(context.Context, string, client.Request, interface{}, client.CallOptions, error) []string
type ServerHandlerObserver func(context.Context, server.Request, interface{}, error) []string
type ServerSubscriberObserver func(context.Context, server.Message, error) []string
type (
ClientCallObserver func(context.Context, client.Request, interface{}, []client.CallOption, error) []string
ClientStreamObserver func(context.Context, client.Request, []client.CallOption, client.Stream, error) []string
ClientPublishObserver func(context.Context, client.Message, []client.PublishOption, error) []string
ClientCallFuncObserver func(context.Context, string, client.Request, interface{}, client.CallOptions, error) []string
ServerHandlerObserver func(context.Context, server.Request, interface{}, error) []string
ServerSubscriberObserver func(context.Context, server.Message, error) []string
)
// Options struct for wrapper
type Options struct {
// Logger that used for log
Logger logger.Logger
// Level for logger
Level logger.Level
// Enabled flag
Enabled bool
// ServerHandlerObservers funcs
ServerHandlerObservers []ServerHandlerObserver
// ServerSubscriberObservers funcs
ServerSubscriberObservers []ServerSubscriberObserver
// ClientCallObservers funcs
ClientCallObservers []ClientCallObserver
// ClientStreamObservers funcs
@@ -90,10 +95,12 @@ type Options struct {
ClientPublishObservers []ClientPublishObserver
// ClientCallFuncObservers funcs
ClientCallFuncObservers []ClientCallFuncObserver
// ServerHandlerObservers funcs
ServerHandlerObservers []ServerHandlerObserver
// ServerSubscriberObservers funcs
ServerSubscriberObservers []ServerSubscriberObserver
// SkipEndpoints
SkipEndpoints []string
// Level for logger
Level logger.Level
// Enabled flag
Enabled bool
}
// Option func signature
@@ -110,6 +117,7 @@ func NewOptions(opts ...Option) Options {
ClientCallFuncObservers: []ClientCallFuncObserver{DefaultClientCallFuncObserver},
ServerHandlerObservers: []ServerHandlerObserver{DefaultServerHandlerObserver},
ServerSubscriberObservers: []ServerSubscriberObserver{DefaultServerSubscriberObserver},
SkipEndpoints: DefaultSkipEndpoints,
}
for _, o := range opts {
@@ -182,9 +190,23 @@ func WithServerSubscriberObservers(ob ...ServerSubscriberObserver) Option {
}
}
// SkipEndpoins
func SkipEndpoints(eps ...string) Option {
return func(o *Options) {
o.SkipEndpoints = append(o.SkipEndpoints, eps...)
}
}
func (l *lWrapper) Call(ctx context.Context, req client.Request, rsp interface{}, opts ...client.CallOption) error {
err := l.Client.Call(ctx, req, rsp, opts...)
endpoint := fmt.Sprintf("%s.%s", req.Service(), req.Endpoint())
for _, ep := range l.opts.SkipEndpoints {
if ep == endpoint {
return err
}
}
if !l.opts.Enabled {
return err
}
@@ -193,7 +215,7 @@ func (l *lWrapper) Call(ctx context.Context, req client.Request, rsp interface{}
for _, o := range l.opts.ClientCallObservers {
labels = append(labels, o(ctx, req, rsp, opts, err)...)
}
fields := make(map[string]interface{}, int(len(labels)/2))
fields := make(map[string]interface{}, len(labels)/2)
for i := 0; i < len(labels); i += 2 {
fields[labels[i]] = labels[i+1]
}
@@ -205,6 +227,13 @@ func (l *lWrapper) Call(ctx context.Context, req client.Request, rsp interface{}
func (l *lWrapper) Stream(ctx context.Context, req client.Request, opts ...client.CallOption) (client.Stream, error) {
stream, err := l.Client.Stream(ctx, req, opts...)
endpoint := fmt.Sprintf("%s.%s", req.Service(), req.Endpoint())
for _, ep := range l.opts.SkipEndpoints {
if ep == endpoint {
return stream, err
}
}
if !l.opts.Enabled {
return stream, err
}
@@ -213,7 +242,7 @@ func (l *lWrapper) Stream(ctx context.Context, req client.Request, opts ...clien
for _, o := range l.opts.ClientStreamObservers {
labels = append(labels, o(ctx, req, opts, stream, err)...)
}
fields := make(map[string]interface{}, int(len(labels)/2))
fields := make(map[string]interface{}, len(labels)/2)
for i := 0; i < len(labels); i += 2 {
fields[labels[i]] = labels[i+1]
}
@@ -225,6 +254,13 @@ func (l *lWrapper) Stream(ctx context.Context, req client.Request, opts ...clien
func (l *lWrapper) Publish(ctx context.Context, msg client.Message, opts ...client.PublishOption) error {
err := l.Client.Publish(ctx, msg, opts...)
endpoint := msg.Topic()
for _, ep := range l.opts.SkipEndpoints {
if ep == endpoint {
return err
}
}
if !l.opts.Enabled {
return err
}
@@ -233,7 +269,7 @@ func (l *lWrapper) Publish(ctx context.Context, msg client.Message, opts ...clie
for _, o := range l.opts.ClientPublishObservers {
labels = append(labels, o(ctx, msg, opts, err)...)
}
fields := make(map[string]interface{}, int(len(labels)/2))
fields := make(map[string]interface{}, len(labels)/2)
for i := 0; i < len(labels); i += 2 {
fields[labels[i]] = labels[i+1]
}
@@ -245,6 +281,13 @@ func (l *lWrapper) Publish(ctx context.Context, msg client.Message, opts ...clie
func (l *lWrapper) ServerHandler(ctx context.Context, req server.Request, rsp interface{}) error {
err := l.serverHandler(ctx, req, rsp)
endpoint := req.Endpoint()
for _, ep := range l.opts.SkipEndpoints {
if ep == endpoint {
return err
}
}
if !l.opts.Enabled {
return err
}
@@ -253,7 +296,7 @@ func (l *lWrapper) ServerHandler(ctx context.Context, req server.Request, rsp in
for _, o := range l.opts.ServerHandlerObservers {
labels = append(labels, o(ctx, req, rsp, err)...)
}
fields := make(map[string]interface{}, int(len(labels)/2))
fields := make(map[string]interface{}, len(labels)/2)
for i := 0; i < len(labels); i += 2 {
fields[labels[i]] = labels[i+1]
}
@@ -265,6 +308,13 @@ func (l *lWrapper) ServerHandler(ctx context.Context, req server.Request, rsp in
func (l *lWrapper) ServerSubscriber(ctx context.Context, msg server.Message) error {
err := l.serverSubscriber(ctx, msg)
endpoint := msg.Topic()
for _, ep := range l.opts.SkipEndpoints {
if ep == endpoint {
return err
}
}
if !l.opts.Enabled {
return err
}
@@ -273,7 +323,7 @@ func (l *lWrapper) ServerSubscriber(ctx context.Context, msg server.Message) err
for _, o := range l.opts.ServerSubscriberObservers {
labels = append(labels, o(ctx, msg, err)...)
}
fields := make(map[string]interface{}, int(len(labels)/2))
fields := make(map[string]interface{}, len(labels)/2)
for i := 0; i < len(labels); i += 2 {
fields[labels[i]] = labels[i+1]
}
@@ -309,6 +359,13 @@ func NewClientCallWrapper(opts ...Option) client.CallWrapper {
func (l *lWrapper) ClientCallFunc(ctx context.Context, addr string, req client.Request, rsp interface{}, opts client.CallOptions) error {
err := l.clientCallFunc(ctx, addr, req, rsp, opts)
endpoint := fmt.Sprintf("%s.%s", req.Service(), req.Endpoint())
for _, ep := range l.opts.SkipEndpoints {
if ep == endpoint {
return err
}
}
if !l.opts.Enabled {
return err
}
@@ -317,7 +374,7 @@ func (l *lWrapper) ClientCallFunc(ctx context.Context, addr string, req client.R
for _, o := range l.opts.ClientCallFuncObservers {
labels = append(labels, o(ctx, addr, req, rsp, opts, err)...)
}
fields := make(map[string]interface{}, int(len(labels)/2))
fields := make(map[string]interface{}, len(labels)/2)
for i := 0; i < len(labels); i += 2 {
fields[labels[i]] = labels[i+1]
}

View File

@@ -5,9 +5,11 @@ import (
"context"
)
type mdIncomingKey struct{}
type mdOutgoingKey struct{}
type mdKey struct{}
type (
mdIncomingKey struct{}
mdOutgoingKey struct{}
mdKey struct{}
)
// FromIncomingContext returns metadata from incoming ctx
// returned metadata shoud not be modified or race condition happens

View File

@@ -7,8 +7,18 @@ import (
)
var (
// HeaderPrefix for all headers passed
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.
@@ -20,10 +30,8 @@ type rawMetadata struct {
md Metadata
}
var (
// defaultMetadataSize used when need to init new Metadata
defaultMetadataSize = 2
)
// defaultMetadataSize used when need to init new Metadata
var defaultMetadataSize = 2
// Iterator used to iterate over metadata with order
type Iterator struct {

View File

@@ -76,7 +76,7 @@ func TestIterator(t *testing.T) {
var k, v string
for iter.Next(&k, &v) {
//fmt.Printf("k: %s, v: %s\n", k, v)
// fmt.Printf("k: %s, v: %s\n", k, v)
}
}
@@ -102,7 +102,6 @@ func TestMedataCanonicalKey(t *testing.T) {
} else if v != "12345" {
t.Fatalf("invalid metadata value: %s != %s", "12345", v)
}
}
func TestMetadataSet(t *testing.T) {
@@ -130,7 +129,6 @@ func TestMetadataDelete(t *testing.T) {
if ok {
t.Fatal("key Baz not deleted")
}
}
func TestNilContext(t *testing.T) {

View File

@@ -1,33 +1,64 @@
package pb
package handler
import (
"bytes"
"context"
"github.com/unistack-org/micro/v3/codec"
"github.com/unistack-org/micro/v3/errors"
"github.com/unistack-org/micro/v3/meter"
)
var (
// guard to fail early
_ MeterServer = &handler{}
)
// guard to fail early
var _ MeterServer = &Handler{}
type Empty struct{}
type handler struct {
meter meter.Meter
opts []meter.Option
type Handler struct {
opts Options
}
func NewHandler(meter meter.Meter, opts ...meter.Option) *handler {
return &handler{meter: meter, opts: opts}
type Option func(*Options)
type Options struct {
Meter meter.Meter
Name string
MeterOptions []meter.Option
}
func (h *handler) Metrics(ctx context.Context, req *Empty, rsp *codec.Frame) error {
func Meter(m meter.Meter) Option {
return func(o *Options) {
o.Meter = m
}
}
func Name(name string) Option {
return func(o *Options) {
o.Name = name
}
}
func MeterOptions(opts ...meter.Option) Option {
return func(o *Options) {
o.MeterOptions = append(o.MeterOptions, opts...)
}
}
func NewOptions(opts ...Option) Options {
options := Options{Meter: meter.DefaultMeter}
for _, o := range opts {
o(&options)
}
return options
}
func NewHandler(opts ...Option) *Handler {
options := NewOptions(opts...)
return &Handler{opts: options}
}
func (h *Handler) Metrics(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error {
buf := bytes.NewBuffer(nil)
if err := h.meter.Write(buf, h.opts...); err != nil {
return err
if err := h.opts.Meter.Write(buf, h.opts.MeterOptions...); err != nil {
return errors.InternalServerError(h.opts.Name, "%v", err)
}
rsp.Data = buf.Bytes()

View File

@@ -1,14 +1,14 @@
syntax = "proto3";
package meter;
option go_package = "github.com/unistack-org/micro/v3/meter/handler;pb";
package micro.meter.handler;
option go_package = "github.com/unistack-org/micro/v3/meter/handler;handler";
import "api/annotations.proto";
import "openapiv2/annotations.proto";
import "codec/frame.proto";
service Meter {
rpc Metrics(Empty) returns (micro.codec.Frame) {
rpc Metrics(micro.codec.Frame) returns (micro.codec.Frame) {
option (micro.openapiv2.openapiv2_operation) = {
operation_id: "Metrics";
responses: {
@@ -17,7 +17,7 @@ service Meter {
description: "Error response";
schema: {
json_schema: {
ref: "Empty";
ref: "micro.codec.Frame";
}
}
}
@@ -26,5 +26,3 @@ service Meter {
option (micro.api.http) = { get: "/metrics"; };
};
};
message Empty{};

View File

@@ -1,6 +1,6 @@
// Code generated by protoc-gen-micro
// source: handler.proto
package pb
package handler
import (
context "context"
@@ -20,5 +20,5 @@ func NewMeterEndpoints() []*api.Endpoint {
}
type MeterServer interface {
Metrics(ctx context.Context, req *Empty, rsp *codec.Frame) error
Metrics(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error
}

View File

@@ -1,6 +1,6 @@
// Code generated by protoc-gen-micro
// source: handler.proto
package pb
package handler
import (
context "context"
@@ -13,20 +13,21 @@ type meterServer struct {
MeterServer
}
func (h *meterServer) Metrics(ctx context.Context, req *Empty, rsp *codec.Frame) error {
func (h *meterServer) Metrics(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error {
return h.MeterServer.Metrics(ctx, req, rsp)
}
func RegisterMeterServer(s server.Server, sh MeterServer, opts ...server.HandlerOption) error {
type meter interface {
Metrics(ctx context.Context, req *Empty, rsp *codec.Frame) error
Metrics(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error
}
type Meter struct {
meter
}
h := &meterServer{sh}
var nopts []server.HandlerOption
for _, endpoint := range NewMeterEndpoints() {
opts = append(opts, api.WithEndpoint(endpoint))
nopts = append(nopts, api.WithEndpoint(endpoint))
}
return s.Handle(s.NewHandler(&Meter{h}, opts...))
return s.Handle(s.NewHandler(&Meter{h}, append(nopts, opts...)...))
}

View File

@@ -3,8 +3,9 @@ package meter
import (
"io"
"reflect"
"sort"
"strconv"
"strings"
"time"
)
@@ -29,13 +30,13 @@ var (
type Meter interface {
Name() string
Init(opts ...Option) error
Counter(name string, opts ...Option) Counter
FloatCounter(name string, opts ...Option) FloatCounter
Gauge(name string, fn func() float64, opts ...Option) Gauge
Counter(name string, labels ...string) Counter
FloatCounter(name string, labels ...string) FloatCounter
Gauge(name string, fn func() float64, labels ...string) Gauge
Set(opts ...Option) Meter
Histogram(name string, opts ...Option) Histogram
Summary(name string, opts ...Option) Summary
SummaryExt(name string, window time.Duration, quantiles []float64, opts ...Option) Summary
Histogram(name string, labels ...string) Histogram
Summary(name string, labels ...string) Summary
SummaryExt(name string, window time.Duration, quantiles []float64, labels ...string) Summary
Write(w io.Writer, opts ...Option) error
Options() Options
String() string
@@ -77,36 +78,62 @@ type Summary interface {
UpdateDuration(time.Time)
}
// sort labels alphabeticaly by label name
type byKey []string
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) 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) {
bk := byKey(*slice)
if bk.Len() <= 1 {
return
// BuildLables used to sort labels and delete duplicates.
// Last value wins in case of duplicate label keys.
func BuildLabels(labels ...string) []string {
if len(labels)%2 == 1 {
labels = labels[:len(labels)-1]
}
sort.Sort(bk)
v := reflect.ValueOf(slice).Elem()
cnt := 0
key := 0
val := 1
for key < v.Len() {
if len(bk) > key+2 && bk[key] == bk[key+2] {
key += 2
val += 2
continue
}
v.Index(cnt).Set(v.Index(key))
cnt++
v.Index(cnt).Set(v.Index(val))
cnt++
key += 2
val += 2
}
v.SetLen(cnt)
sort.Sort(byKey(labels))
return labels
}
// BuildName used to combine metric with labels.
// If labels count is odd, drop last element
func BuildName(name string, labels ...string) string {
if len(labels)%2 == 1 {
labels = labels[:len(labels)-1]
}
if len(labels) > 2 {
sort.Sort(byKey(labels))
idx := 0
for {
if labels[idx] == labels[idx+2] {
copy(labels[idx:], labels[idx+2:])
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

@@ -10,15 +10,61 @@ func TestNoopMeter(t *testing.T) {
t.Fatalf("invalid options parsing: %v", m.Options())
}
cnt := m.Counter("counter", Labels("server", "noop"))
cnt := m.Counter("counter", "server", "noop")
cnt.Inc()
}
func TestLabelsSort(t *testing.T) {
ls := []string{"server", "http", "register", "mdns", "broker", "broker1", "broker", "broker2", "server", "tcp"}
Sort(&ls)
func testEq(a, b []string) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}
if ls[0] != "broker" || ls[1] != "broker2" {
t.Fatalf("sort error: %v", ls)
func TestBuildLabels(t *testing.T) {
type testData struct {
src []string
dst []string
}
data := []testData{
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"}`: []string{
"my_metric",
"zerolabel", "value3", "firstlabel", "value2",
},
`my_metric{broker="broker2",register="mdns",server="tcp"}`: []string{
"my_metric",
"broker", "broker1", "broker", "broker2", "server", "http", "server", "tcp", "register", "mdns",
},
`my_metric{aaa="aaa"}`: []string{
"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

@@ -28,57 +28,33 @@ func (r *noopMeter) Init(opts ...Option) error {
}
// Counter implements the Meter interface
func (r *noopMeter) Counter(name string, opts ...Option) Counter {
options := Options{}
for _, o := range opts {
o(&options)
}
return &noopCounter{labels: options.Labels}
func (r *noopMeter) Counter(name string, labels ...string) Counter {
return &noopCounter{labels: labels}
}
// FloatCounter implements the Meter interface
func (r *noopMeter) FloatCounter(name string, opts ...Option) FloatCounter {
options := Options{}
for _, o := range opts {
o(&options)
}
return &noopFloatCounter{labels: options.Labels}
func (r *noopMeter) FloatCounter(name string, labels ...string) FloatCounter {
return &noopFloatCounter{labels: labels}
}
// Gauge implements the Meter interface
func (r *noopMeter) Gauge(name string, f func() float64, opts ...Option) Gauge {
options := Options{}
for _, o := range opts {
o(&options)
}
return &noopGauge{labels: options.Labels}
func (r *noopMeter) Gauge(name string, f func() float64, labels ...string) Gauge {
return &noopGauge{labels: labels}
}
// Summary implements the Meter interface
func (r *noopMeter) Summary(name string, opts ...Option) Summary {
options := Options{}
for _, o := range opts {
o(&options)
}
return &noopSummary{labels: options.Labels}
func (r *noopMeter) Summary(name string, labels ...string) Summary {
return &noopSummary{labels: labels}
}
// SummaryExt implements the Meter interface
func (r *noopMeter) SummaryExt(name string, window time.Duration, quantiles []float64, opts ...Option) Summary {
options := Options{}
for _, o := range opts {
o(&options)
}
return &noopSummary{labels: options.Labels}
func (r *noopMeter) SummaryExt(name string, window time.Duration, quantiles []float64, labels ...string) Summary {
return &noopSummary{labels: labels}
}
// Histogram implements the Meter interface
func (r *noopMeter) Histogram(name string, opts ...Option) Histogram {
options := Options{}
for _, o := range opts {
o(&options)
}
return &noopHistogram{labels: options.Labels}
func (r *noopMeter) Histogram(name string, labels ...string) Histogram {
return &noopHistogram{labels: labels}
}
// Set implements the Meter interface
@@ -111,11 +87,9 @@ type noopCounter struct {
}
func (r *noopCounter) Add(int) {
}
func (r *noopCounter) Dec() {
}
func (r *noopCounter) Get() uint64 {
@@ -123,11 +97,9 @@ func (r *noopCounter) Get() uint64 {
}
func (r *noopCounter) Inc() {
}
func (r *noopCounter) Set(uint64) {
}
type noopFloatCounter struct {
@@ -135,7 +107,6 @@ type noopFloatCounter struct {
}
func (r *noopFloatCounter) Add(float64) {
}
func (r *noopFloatCounter) Get() float64 {
@@ -143,11 +114,9 @@ func (r *noopFloatCounter) Get() float64 {
}
func (r *noopFloatCounter) Set(float64) {
}
func (r *noopFloatCounter) Sub(float64) {
}
type noopGauge struct {
@@ -163,11 +132,9 @@ type noopSummary struct {
}
func (r *noopSummary) Update(float64) {
}
func (r *noopSummary) UpdateDuration(time.Time) {
}
type noopHistogram struct {
@@ -175,15 +142,12 @@ type noopHistogram struct {
}
func (r *noopHistogram) Reset() {
}
func (r *noopHistogram) Update(float64) {
}
func (r *noopHistogram) UpdateDuration(time.Time) {
}
//func (r *noopHistogram) VisitNonZeroBuckets(f func(vmrange string, count uint64)) {}
// func (r *noopHistogram) VisitNonZeroBuckets(f func(vmrange string, count uint64)) {}

View File

@@ -90,7 +90,7 @@ func Logger(l logger.Logger) Option {
func Labels(ls ...string) Option {
return func(o *Options) {
o.Labels = ls
o.Labels = append(o.Labels, ls...)
}
}

View File

@@ -11,36 +11,48 @@ import (
)
var (
ClientRequestDurationSeconds = "client_request_duration_seconds"
ClientRequestLatencyMicroseconds = "client_request_latency_microseconds"
ClientRequestTotal = "client_request_total"
ServerRequestDurationSeconds = "server_request_duration_seconds"
ServerRequestLatencyMicroseconds = "server_request_latency_microseconds"
ServerRequestTotal = "server_request_total"
PublishMessageDurationSeconds = "publish_message_duration_seconds"
PublishMessageLatencyMicroseconds = "publish_message_latency_microseconds"
PublishMessageTotal = "publish_message_total"
ClientRequestDurationSeconds = "client_request_duration_seconds"
ClientRequestLatencyMicroseconds = "client_request_latency_microseconds"
ClientRequestTotal = "client_request_total"
ClientRequestInflight = "client_request_inflight"
ServerRequestDurationSeconds = "server_request_duration_seconds"
ServerRequestLatencyMicroseconds = "server_request_latency_microseconds"
ServerRequestTotal = "server_request_total"
ServerRequestInflight = "server_request_inflight"
PublishMessageDurationSeconds = "publish_message_duration_seconds"
PublishMessageLatencyMicroseconds = "publish_message_latency_microseconds"
PublishMessageTotal = "publish_message_total"
PublishMessageInflight = "publish_message_inflight"
SubscribeMessageDurationSeconds = "subscribe_message_duration_seconds"
SubscribeMessageLatencyMicroseconds = "subscribe_message_latency_microseconds"
SubscribeMessageTotal = "subscribe_message_total"
SubscribeMessageInflight = "subscribe_message_inflight"
labelSuccess = "success"
labelFailure = "failure"
labelStatus = "status"
labelEndpoint = "endpoint"
// DefaultSkipEndpoints contains list of endpoints that not evaluted by wrapper
DefaultSkipEndpoints = []string{"Meter.Metrics"}
)
type Options struct {
Meter meter.Meter
lopts []meter.Option
Meter meter.Meter
lopts []meter.Option
SkipEndpoints []string
}
type Option func(*Options)
func NewOptions(opts ...Option) Options {
options := Options{
Meter: meter.DefaultMeter,
lopts: make([]meter.Option, 0, 5),
Meter: meter.DefaultMeter,
lopts: make([]meter.Option, 0, 5),
SkipEndpoints: DefaultSkipEndpoints,
}
for _, o := range opts {
o(&options)
@@ -72,6 +84,12 @@ func Meter(m meter.Meter) Option {
}
}
func SkipEndoints(eps ...string) Option {
return func(o *Options) {
o.SkipEndpoints = append(o.SkipEndpoints, eps...)
}
}
type wrapper struct {
client.Client
callFunc client.CallFunc
@@ -100,69 +118,90 @@ func NewCallWrapper(opts ...Option) client.CallWrapper {
func (w *wrapper) CallFunc(ctx context.Context, addr string, req client.Request, rsp interface{}, opts client.CallOptions) error {
endpoint := fmt.Sprintf("%s.%s", req.Service(), req.Endpoint())
for _, ep := range w.opts.SkipEndpoints {
if ep == endpoint {
return w.callFunc(ctx, addr, req, rsp, opts)
}
}
labels := make([]string, 0, 4)
labels = append(labels, labelEndpoint, endpoint)
w.opts.Meter.Counter(ClientRequestInflight, labels...).Inc()
ts := time.Now()
err := w.callFunc(ctx, addr, req, rsp, opts)
te := time.Since(ts)
w.opts.Meter.Counter(ClientRequestInflight, labels...).Dec()
lopts := w.opts.lopts
lopts = append(lopts, meter.Labels(labelEndpoint, endpoint))
w.opts.Meter.Summary(ClientRequestLatencyMicroseconds, lopts...).Update(float64(te.Seconds()))
w.opts.Meter.Histogram(ClientRequestDurationSeconds, lopts...).Update(float64(te.Seconds()))
w.opts.Meter.Summary(ClientRequestLatencyMicroseconds, labels...).Update(te.Seconds())
w.opts.Meter.Histogram(ClientRequestDurationSeconds, labels...).Update(te.Seconds())
if err == nil {
lopts = append(lopts, meter.Labels(labelStatus, labelSuccess))
labels = append(labels, labelStatus, labelSuccess)
} else {
lopts = append(lopts, meter.Labels(labelStatus, labelFailure))
labels = append(labels, labelStatus, labelFailure)
}
w.opts.Meter.Counter(ClientRequestTotal, lopts...).Inc()
w.opts.Meter.Counter(ClientRequestTotal, labels...).Inc()
return err
}
func (w *wrapper) Call(ctx context.Context, req client.Request, rsp interface{}, opts ...client.CallOption) error {
endpoint := fmt.Sprintf("%s.%s", req.Service(), req.Endpoint())
for _, ep := range w.opts.SkipEndpoints {
if ep == endpoint {
return w.Client.Call(ctx, req, rsp, opts...)
}
}
labels := make([]string, 0, 4)
labels = append(labels, labelEndpoint, endpoint)
w.opts.Meter.Counter(ClientRequestInflight, labels...).Inc()
ts := time.Now()
err := w.Client.Call(ctx, req, rsp, opts...)
te := time.Since(ts)
w.opts.Meter.Counter(ClientRequestInflight, labels...).Dec()
lopts := w.opts.lopts
lopts = append(lopts, meter.Labels(labelEndpoint, endpoint))
w.opts.Meter.Summary(ClientRequestLatencyMicroseconds, lopts...).Update(float64(te.Seconds()))
w.opts.Meter.Histogram(ClientRequestDurationSeconds, lopts...).Update(float64(te.Seconds()))
w.opts.Meter.Summary(ClientRequestLatencyMicroseconds, labels...).Update(te.Seconds())
w.opts.Meter.Histogram(ClientRequestDurationSeconds, labels...).Update(te.Seconds())
if err == nil {
lopts = append(lopts, meter.Labels(labelStatus, labelSuccess))
labels = append(labels, labelStatus, labelSuccess)
} else {
lopts = append(lopts, meter.Labels(labelStatus, labelFailure))
labels = append(labels, labelStatus, labelFailure)
}
w.opts.Meter.Counter(ClientRequestTotal, lopts...).Inc()
w.opts.Meter.Counter(ClientRequestTotal, labels...).Inc()
return err
}
func (w *wrapper) Stream(ctx context.Context, req client.Request, opts ...client.CallOption) (client.Stream, error) {
endpoint := fmt.Sprintf("%s.%s", req.Service(), req.Endpoint())
for _, ep := range w.opts.SkipEndpoints {
if ep == endpoint {
return w.Client.Stream(ctx, req, opts...)
}
}
labels := make([]string, 0, 4)
labels = append(labels, labelEndpoint, endpoint)
w.opts.Meter.Counter(ClientRequestInflight, labels...).Inc()
ts := time.Now()
stream, err := w.Client.Stream(ctx, req, opts...)
te := time.Since(ts)
w.opts.Meter.Counter(ClientRequestInflight, labels...).Dec()
lopts := w.opts.lopts
lopts = append(lopts, meter.Labels(labelEndpoint, endpoint))
w.opts.Meter.Summary(ClientRequestLatencyMicroseconds, lopts...).Update(float64(te.Seconds()))
w.opts.Meter.Histogram(ClientRequestDurationSeconds, lopts...).Update(float64(te.Seconds()))
w.opts.Meter.Summary(ClientRequestLatencyMicroseconds, labels...).Update(te.Seconds())
w.opts.Meter.Histogram(ClientRequestDurationSeconds, labels...).Update(te.Seconds())
if err == nil {
lopts = append(lopts, meter.Labels(labelStatus, labelSuccess))
labels = append(labels, labelStatus, labelSuccess)
} else {
lopts = append(lopts, meter.Labels(labelStatus, labelFailure))
labels = append(labels, labelStatus, labelFailure)
}
w.opts.Meter.Counter(ClientRequestTotal, lopts...).Inc()
w.opts.Meter.Counter(ClientRequestTotal, labels...).Inc()
return stream, err
}
@@ -170,22 +209,24 @@ func (w *wrapper) Stream(ctx context.Context, req client.Request, opts ...client
func (w *wrapper) Publish(ctx context.Context, p client.Message, opts ...client.PublishOption) error {
endpoint := p.Topic()
labels := make([]string, 0, 4)
labels = append(labels, labelEndpoint, endpoint)
w.opts.Meter.Counter(PublishMessageInflight, labels...).Inc()
ts := time.Now()
err := w.Client.Publish(ctx, p, opts...)
te := time.Since(ts)
w.opts.Meter.Counter(PublishMessageInflight, labels...).Dec()
lopts := w.opts.lopts
lopts = append(lopts, meter.Labels(labelEndpoint, endpoint))
w.opts.Meter.Summary(PublishMessageLatencyMicroseconds, lopts...).Update(float64(te.Seconds()))
w.opts.Meter.Histogram(PublishMessageDurationSeconds, lopts...).Update(float64(te.Seconds()))
w.opts.Meter.Summary(PublishMessageLatencyMicroseconds, labels...).Update(te.Seconds())
w.opts.Meter.Histogram(PublishMessageDurationSeconds, labels...).Update(te.Seconds())
if err == nil {
lopts = append(lopts, meter.Labels(labelStatus, labelSuccess))
labels = append(labels, labelStatus, labelSuccess)
} else {
lopts = append(lopts, meter.Labels(labelStatus, labelFailure))
labels = append(labels, labelStatus, labelFailure)
}
w.opts.Meter.Counter(PublishMessageTotal, lopts...).Inc()
w.opts.Meter.Counter(PublishMessageTotal, labels...).Inc()
return err
}
@@ -200,23 +241,30 @@ func NewHandlerWrapper(opts ...Option) server.HandlerWrapper {
func (w *wrapper) HandlerFunc(fn server.HandlerFunc) server.HandlerFunc {
return func(ctx context.Context, req server.Request, rsp interface{}) error {
endpoint := req.Endpoint()
for _, ep := range w.opts.SkipEndpoints {
if ep == endpoint {
return fn(ctx, req, rsp)
}
}
labels := make([]string, 0, 4)
labels = append(labels, labelEndpoint, endpoint)
w.opts.Meter.Counter(ServerRequestInflight, labels...).Inc()
ts := time.Now()
err := fn(ctx, req, rsp)
te := time.Since(ts)
w.opts.Meter.Counter(ServerRequestInflight, labels...).Dec()
lopts := w.opts.lopts
lopts = append(lopts, meter.Labels(labelEndpoint, endpoint))
w.opts.Meter.Summary(ServerRequestLatencyMicroseconds, lopts...).Update(float64(te.Seconds()))
w.opts.Meter.Histogram(ServerRequestDurationSeconds, lopts...).Update(float64(te.Seconds()))
w.opts.Meter.Summary(ServerRequestLatencyMicroseconds, labels...).Update(te.Seconds())
w.opts.Meter.Histogram(ServerRequestDurationSeconds, labels...).Update(te.Seconds())
if err == nil {
lopts = append(lopts, meter.Labels(labelStatus, labelSuccess))
labels = append(labels, labelStatus, labelSuccess)
} else {
lopts = append(lopts, meter.Labels(labelStatus, labelFailure))
labels = append(labels, labelStatus, labelFailure)
}
w.opts.Meter.Counter(ServerRequestTotal, lopts...).Inc()
w.opts.Meter.Counter(ServerRequestTotal, labels...).Inc()
return err
}
@@ -233,22 +281,24 @@ func (w *wrapper) SubscriberFunc(fn server.SubscriberFunc) server.SubscriberFunc
return func(ctx context.Context, msg server.Message) error {
endpoint := msg.Topic()
labels := make([]string, 0, 4)
labels = append(labels, labelEndpoint, endpoint)
w.opts.Meter.Counter(SubscribeMessageInflight, labels...).Inc()
ts := time.Now()
err := fn(ctx, msg)
te := time.Since(ts)
w.opts.Meter.Counter(SubscribeMessageInflight, labels...).Dec()
lopts := w.opts.lopts
lopts = append(lopts, meter.Labels(labelEndpoint, endpoint))
w.opts.Meter.Summary(SubscribeMessageLatencyMicroseconds, lopts...).Update(float64(te.Seconds()))
w.opts.Meter.Histogram(SubscribeMessageDurationSeconds, lopts...).Update(float64(te.Seconds()))
w.opts.Meter.Summary(SubscribeMessageLatencyMicroseconds, labels...).Update(te.Seconds())
w.opts.Meter.Histogram(SubscribeMessageDurationSeconds, labels...).Update(te.Seconds())
if err == nil {
lopts = append(lopts, meter.Labels(labelStatus, labelSuccess))
labels = append(labels, labelStatus, labelSuccess)
} else {
lopts = append(lopts, meter.Labels(labelStatus, labelFailure))
labels = append(labels, labelStatus, labelFailure)
}
w.opts.Meter.Counter(SubscribeMessageTotal, lopts...).Inc()
w.opts.Meter.Counter(SubscribeMessageTotal, labels...).Inc()
return err
}

View File

@@ -31,18 +31,18 @@ type memoryClient struct {
}
type memoryListener struct {
topts Options
ctx context.Context
lopts ListenOptions
ctx context.Context
exit chan bool
conn chan *memorySocket
addr string
topts Options
sync.RWMutex
}
type memoryTransport struct {
opts Options
listeners map[string]*memoryListener
opts Options
sync.RWMutex
}

View File

@@ -27,9 +27,9 @@ func TestMemoryTransport(t *testing.T) {
if len(os.Getenv("INTEGRATION_TESTS")) == 0 {
t.Logf("Server Received %s", string(m.Body))
}
if err := sock.Send(&Message{
if cerr := sock.Send(&Message{
Body: []byte(`pong`),
}); err != nil {
}); cerr != nil {
return
}
}
@@ -60,7 +60,6 @@ func TestMemoryTransport(t *testing.T) {
t.Logf("Client Received %s", string(m.Body))
}
}
}
func TestListener(t *testing.T) {

View File

@@ -12,26 +12,37 @@ import (
)
type tunBroker struct {
opts broker.Options
tunnel tunnel.Tunnel
opts broker.Options
}
type tunSubscriber struct {
opts broker.SubscribeOptions
listener tunnel.Listener
handler broker.Handler
closed chan bool
topic string
opts broker.SubscribeOptions
}
type tunBatchSubscriber struct {
listener tunnel.Listener
handler broker.BatchHandler
closed chan bool
topic string
opts broker.SubscribeOptions
}
type tunEvent struct {
message *broker.Message
topic string
err error
}
// used to access tunnel from options context
type tunnelKey struct{}
type tunnelAddr struct{}
type (
tunnelKey struct{}
tunnelAddr struct{}
)
func (t *tunBroker) Init(opts ...broker.Option) error {
for _, o := range opts {
@@ -60,6 +71,36 @@ func (t *tunBroker) Disconnect(ctx context.Context) error {
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 {
// TODO: this is probably inefficient, we might want to just maintain an open connection
// it may be easier to add broadcast to the tunnel
@@ -75,6 +116,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) {
l, err := t.tunnel.Listen(ctx, topic, tunnel.ListenMode(tunnel.Multicast))
if err != nil {
@@ -99,6 +160,49 @@ func (t *tunBroker) String() string {
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 t.handler(evts)
}
}
func (t *tunSubscriber) run() {
for {
// accept a new connection
@@ -140,6 +244,24 @@ func (t *tunSubscriber) run() {
}
}
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()
}
}
func (t *tunSubscriber) Options() broker.SubscribeOptions {
return t.opts
}
@@ -171,7 +293,11 @@ func (t *tunEvent) Ack() 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

View File

@@ -34,8 +34,8 @@ type Options struct {
Token string
// Name holds the tunnel name
Name string
// Id holds the tunnel id
Id string
// ID holds the tunnel id
ID string
// Address holds the tunnel address
Address string
// Nodes holds the tunnel nodes
@@ -68,10 +68,10 @@ type ListenOptions struct {
Timeout time.Duration
}
// Id sets the tunnel id
func Id(id string) Option {
// ID sets the tunnel id
func ID(id string) Option {
return func(o *Options) {
o.Id = id
o.ID = id
}
}
@@ -164,7 +164,7 @@ func DialWait(b bool) DialOption {
// NewOptions returns router default options with filled values
func NewOptions(opts ...Option) Options {
options := Options{
Id: uuid.New().String(),
ID: uuid.New().String(),
Address: DefaultAddress,
Token: DefaultToken,
Logger: logger.DefaultLogger,

View File

@@ -10,9 +10,8 @@ import (
)
type tunTransport struct {
tunnel tunnel.Tunnel
options transport.Options
tunnel tunnel.Tunnel
}
type tunnelKey struct{}
@@ -88,7 +87,7 @@ func NewTransport(opts ...transport.Option) transport.Transport {
}
// initialise
//t.Init(opts...)
// t.Init(opts...)
return t
}

View File

@@ -9,10 +9,8 @@ import (
"github.com/unistack-org/micro/v3/network/transport"
)
var (
// DefaultTunnel contains default tunnel implementation
DefaultTunnel Tunnel
)
// DefaultTunnel contains default tunnel implementation
var DefaultTunnel Tunnel
const (
// Unicast send over one link

View File

@@ -17,8 +17,6 @@ import (
"github.com/unistack-org/micro/v3/server"
"github.com/unistack-org/micro/v3/store"
"github.com/unistack-org/micro/v3/tracer"
// "github.com/unistack-org/micro/v3/profiler"
// "github.com/unistack-org/micro/v3/runtime"
)
// Options for micro service
@@ -78,8 +76,8 @@ func NewOptions(opts ...Option) Options {
Meters: []meter.Meter{meter.DefaultMeter},
Configs: []config.Config{config.DefaultConfig},
Stores: []store.Store{store.DefaultStore},
//Runtime runtime.Runtime
//Profile profile.Profile
// Runtime runtime.Runtime
// Profile profile.Profile
}
for _, o := range opts {

View File

@@ -16,10 +16,8 @@ type httpProfile struct {
running bool
}
var (
// DefaultAddress for http profiler
DefaultAddress = ":6060"
)
// DefaultAddress for http profiler
var DefaultAddress = ":6060"
// Start the profiler
func (h *httpProfile) Start() error {

View File

@@ -11,10 +11,8 @@ type Profiler interface {
String() string
}
var (
// DefaultProfiler holds the default profiler
DefaultProfiler Profiler = NewProfiler()
)
// DefaultProfiler holds the default profiler
var DefaultProfiler Profiler = NewProfiler()
// Options holds the options for profiler
type Options struct {

View File

@@ -7,10 +7,8 @@ import (
"github.com/unistack-org/micro/v3/server"
)
var (
// DefaultEndpoint holds default proxy address
DefaultEndpoint = "localhost:9090"
)
// DefaultEndpoint holds default proxy address
var DefaultEndpoint = "localhost:9090"
// Proxy can be used as a proxy server for micro services
type Proxy interface {

View File

@@ -3,7 +3,6 @@ package register
import (
"fmt"
"reflect"
"strings"
"unicode"
"unicode/utf8"
@@ -11,12 +10,12 @@ import (
)
// ExtractValue from reflect.Type from specified depth
func ExtractValue(v reflect.Type, d int) *Value {
func ExtractValue(v reflect.Type, d int) string {
if d == 3 {
return nil
return ""
}
if v == nil {
return nil
return ""
}
if v.Kind() == reflect.Ptr {
@@ -25,7 +24,7 @@ func ExtractValue(v reflect.Type, d int) *Value {
// slices and maps don't have a defined name
if (v.Kind() == reflect.Slice || v.Kind() == reflect.Map) || len(v.Name()) == 0 {
return nil
return ""
}
// get the rune character
@@ -33,58 +32,10 @@ func ExtractValue(v reflect.Type, d int) *Value {
// crude check for is unexported field
if unicode.IsLower(a) {
return nil
return ""
}
arg := &Value{
Name: v.Name(),
Type: v.Name(),
}
switch v.Kind() {
case reflect.Struct:
for i := 0; i < v.NumField(); i++ {
f := v.Field(i)
val := ExtractValue(f.Type, d+1)
if val == nil {
continue
}
// if we can find a json tag use it
if tags := f.Tag.Get("json"); len(tags) > 0 {
parts := strings.Split(tags, ",")
if parts[0] == "-" || parts[0] == "omitempty" {
continue
}
val.Name = parts[0]
}
// if there's no name default it
if len(val.Name) == 0 {
val.Name = v.Field(i).Name
}
arg.Values = append(arg.Values, val)
}
case reflect.Slice:
p := v.Elem()
if p.Kind() == reflect.Ptr {
p = p.Elem()
}
arg.Type = "[]" + p.Name()
case reflect.Map:
p := v.Elem()
if p.Kind() == reflect.Ptr {
p = p.Elem()
}
key := v.Key()
if key.Kind() == reflect.Ptr {
key = key.Elem()
}
arg.Type = fmt.Sprintf("map[%s]%s", key.Name(), p.Name())
}
return arg
return v.Name()
}
// ExtractEndpoint extract *Endpoint from reflect.Method
@@ -116,7 +67,7 @@ func ExtractEndpoint(method reflect.Method) *Endpoint {
request := ExtractValue(reqType, 0)
response := ExtractValue(rspType, 0)
if request == nil || response == nil {
if request == "" || response == "" {
return nil
}
@@ -135,7 +86,7 @@ func ExtractEndpoint(method reflect.Method) *Endpoint {
}
// ExtractSubValue exctact *Value from reflect.Type
func ExtractSubValue(typ reflect.Type) *Value {
func ExtractSubValue(typ reflect.Type) string {
var reqType reflect.Type
switch typ.NumIn() {
case 1:
@@ -145,7 +96,7 @@ func ExtractSubValue(typ reflect.Type) *Value {
case 3:
reqType = typ.In(2)
default:
return nil
return ""
}
return ExtractValue(reqType, 0)
}

View File

@@ -36,28 +36,21 @@ func TestExtractEndpoint(t *testing.T) {
t.Fatalf("Expected handler Test, got %s", endpoints[0].Name)
}
if endpoints[0].Request == nil {
if endpoints[0].Request == "" {
t.Fatal("Expected non nil Request")
}
if endpoints[0].Response == nil {
if endpoints[0].Response == "" {
t.Fatal("Expected non nil Request")
}
if endpoints[0].Request.Name != "TestRequest" {
t.Fatalf("Expected TestRequest got %s", endpoints[0].Request.Name)
if endpoints[0].Request != "TestRequest" {
t.Fatalf("Expected TestRequest got %s", endpoints[0].Request)
}
if endpoints[0].Response.Name != "TestResponse" {
t.Fatalf("Expected TestResponse got %s", endpoints[0].Response.Name)
}
if endpoints[0].Request.Type != "TestRequest" {
t.Fatalf("Expected TestRequest type got %s", endpoints[0].Request.Type)
}
if endpoints[0].Response.Type != "TestResponse" {
t.Fatalf("Expected TestResponse type got %s", endpoints[0].Response.Type)
if endpoints[0].Response != "TestResponse" {
t.Fatalf("Expected TestResponse got %s", endpoints[0].Response)
}
t.Logf("XXX %#+v\n", endpoints[0])
}

View File

@@ -30,10 +30,9 @@ type record struct {
}
type memory struct {
opts Options
// records is a KV map with domain name as the key and a services map as the value
records map[string]services
watchers map[string]*watcher
opts Options
sync.RWMutex
}
@@ -65,7 +64,7 @@ func (m *memory) ttlPrune() {
for id, n := range record.Nodes {
if n.TTL != 0 && time.Since(n.LastSeen) > n.TTL {
if m.opts.Logger.V(logger.DebugLevel) {
m.opts.Logger.Debugf(m.opts.Context, "Register TTL expired for node %s of service %s", n.Id, service)
m.opts.Logger.Debugf(m.opts.Context, "Register TTL expired for node %s of service %s", n.ID, service)
}
delete(m.records[domain][service][version].Nodes, id)
}
@@ -162,7 +161,7 @@ func (m *memory) Register(ctx context.Context, s *Service, opts ...RegisterOptio
for _, n := range s.Nodes {
// check if already exists
if _, ok := srvs[s.Name][s.Version].Nodes[n.Id]; ok {
if _, ok := srvs[s.Name][s.Version].Nodes[n.ID]; ok {
continue
}
@@ -177,9 +176,9 @@ func (m *memory) Register(ctx context.Context, s *Service, opts ...RegisterOptio
metadata["domain"] = options.Domain
// add the node
srvs[s.Name][s.Version].Nodes[n.Id] = &node{
srvs[s.Name][s.Version].Nodes[n.ID] = &node{
Node: &Node{
Id: n.Id,
ID: n.ID,
Address: n.Address,
Metadata: metadata,
},
@@ -201,8 +200,8 @@ func (m *memory) Register(ctx context.Context, s *Service, opts ...RegisterOptio
if m.opts.Logger.V(logger.DebugLevel) {
m.opts.Logger.Debugf(m.opts.Context, "Updated registration for service: %s, version: %s", s.Name, s.Version)
}
srvs[s.Name][s.Version].Nodes[n.Id].TTL = options.TTL
srvs[s.Name][s.Version].Nodes[n.Id].LastSeen = time.Now()
srvs[s.Name][s.Version].Nodes[n.ID].TTL = options.TTL
srvs[s.Name][s.Version].Nodes[n.ID].LastSeen = time.Now()
}
}
@@ -242,11 +241,11 @@ func (m *memory) Deregister(ctx context.Context, s *Service, opts ...DeregisterO
// deregister all of the service nodes from this version
for _, n := range s.Nodes {
if _, ok := version.Nodes[n.Id]; ok {
if _, ok := version.Nodes[n.ID]; ok {
if m.opts.Logger.V(logger.DebugLevel) {
m.opts.Logger.Debugf(m.opts.Context, "Register removed node from service: %s, version: %s", s.Name, s.Version)
}
delete(version.Nodes, n.Id)
delete(version.Nodes, n.ID)
}
}
@@ -458,7 +457,7 @@ func serviceToRecord(s *Service, ttl time.Duration) *record {
nodes := make(map[string]*node, len(s.Nodes))
for _, n := range s.Nodes {
nodes[n.Id] = &node{
nodes[n.ID] = &node{
Node: n,
TTL: ttl,
LastSeen: time.Now(),
@@ -490,40 +489,31 @@ func recordToService(r *record, domain string) *Service {
endpoints := make([]*Endpoint, len(r.Endpoints))
for i, e := range r.Endpoints {
request := new(Value)
if e.Request != nil {
*request = *e.Request
}
response := new(Value)
if e.Response != nil {
*response = *e.Response
}
metadata := make(map[string]string, len(e.Metadata))
md := make(map[string]string, len(e.Metadata))
for k, v := range e.Metadata {
metadata[k] = v
md[k] = v
}
endpoints[i] = &Endpoint{
Name: e.Name,
Request: request,
Response: response,
Metadata: metadata,
Request: e.Request,
Response: e.Response,
Metadata: md,
}
}
nodes := make([]*Node, len(r.Nodes))
i := 0
for _, n := range r.Nodes {
metadata := make(map[string]string, len(n.Metadata))
md := make(map[string]string, len(n.Metadata))
for k, v := range n.Metadata {
metadata[k] = v
md[k] = v
}
nodes[i] = &Node{
Id: n.Id,
ID: n.ID,
Address: n.Address,
Metadata: metadata,
Metadata: md,
}
i++
}

View File

@@ -8,72 +8,70 @@ import (
"time"
)
var (
testData = map[string][]*Service{
"foo": {
{
Name: "foo",
Version: "1.0.0",
Nodes: []*Node{
{
Id: "foo-1.0.0-123",
Address: "localhost:9999",
},
{
Id: "foo-1.0.0-321",
Address: "localhost:9999",
},
var testData = map[string][]*Service{
"foo": {
{
Name: "foo",
Version: "1.0.0",
Nodes: []*Node{
{
ID: "foo-1.0.0-123",
Address: "localhost:9999",
},
},
{
Name: "foo",
Version: "1.0.1",
Nodes: []*Node{
{
Id: "foo-1.0.1-321",
Address: "localhost:6666",
},
},
},
{
Name: "foo",
Version: "1.0.3",
Nodes: []*Node{
{
Id: "foo-1.0.3-345",
Address: "localhost:8888",
},
{
ID: "foo-1.0.0-321",
Address: "localhost:9999",
},
},
},
"bar": {
{
Name: "bar",
Version: "default",
Nodes: []*Node{
{
Id: "bar-1.0.0-123",
Address: "localhost:9999",
},
{
Id: "bar-1.0.0-321",
Address: "localhost:9999",
},
},
},
{
Name: "bar",
Version: "latest",
Nodes: []*Node{
{
Id: "bar-1.0.1-321",
Address: "localhost:6666",
},
{
Name: "foo",
Version: "1.0.1",
Nodes: []*Node{
{
ID: "foo-1.0.1-321",
Address: "localhost:6666",
},
},
},
}
)
{
Name: "foo",
Version: "1.0.3",
Nodes: []*Node{
{
ID: "foo-1.0.3-345",
Address: "localhost:8888",
},
},
},
},
"bar": {
{
Name: "bar",
Version: "default",
Nodes: []*Node{
{
ID: "bar-1.0.0-123",
Address: "localhost:9999",
},
{
ID: "bar-1.0.0-321",
Address: "localhost:9999",
},
},
},
{
Name: "bar",
Version: "latest",
Nodes: []*Node{
{
ID: "bar-1.0.1-321",
Address: "localhost:6666",
},
},
},
},
}
//nolint:gocyclo
func TestMemoryRegistry(t *testing.T) {

View File

@@ -44,6 +44,7 @@ func NewOptions(opts ...Option) Options {
return options
}
// nolint: golint
// RegisterOptions holds options for register method
type RegisterOptions struct {
Context context.Context
@@ -196,6 +197,7 @@ func TLSConfig(t *tls.Config) Option {
}
}
// nolint: golint
// RegisterAttempts specifies register atempts count
func RegisterAttempts(t int) RegisterOption {
return func(o *RegisterOptions) {
@@ -203,6 +205,7 @@ func RegisterAttempts(t int) RegisterOption {
}
}
// nolint: golint
// RegisterTTL specifies register ttl
func RegisterTTL(t time.Duration) RegisterOption {
return func(o *RegisterOptions) {
@@ -210,6 +213,7 @@ func RegisterTTL(t time.Duration) RegisterOption {
}
}
// nolint: golint
// RegisterContext sets the register context
func RegisterContext(ctx context.Context) RegisterOption {
return func(o *RegisterOptions) {
@@ -217,6 +221,7 @@ func RegisterContext(ctx context.Context) RegisterOption {
}
}
// nolint: golint
// RegisterDomain secifies register domain
func RegisterDomain(d string) RegisterOption {
return func(o *RegisterOptions) {

View File

@@ -53,29 +53,23 @@ type Service struct {
// Node holds node register info
type Node struct {
Metadata metadata.Metadata `json:"metadata"`
Id string `json:"id"`
ID string `json:"id"`
Address string `json:"address"`
}
// Endpoint holds endpoint register info
type Endpoint struct {
Request *Value `json:"request"`
Response *Value `json:"response"`
Request string `json:"request"`
Response string `json:"response"`
Metadata metadata.Metadata `json:"metadata"`
Name string `json:"name"`
}
// Value holds additional kv stuff
type Value struct {
Name string `json:"name"`
Type string `json:"type"`
Values []*Value `json:"values"`
}
// Option func signature
type Option func(*Options)
// RegisterOption option is used to register service
// nolint: golint
type RegisterOption func(*RegisterOptions)
// WatchOption option is used to watch service changes

View File

@@ -52,8 +52,8 @@ type Event struct {
Timestamp time.Time
// Service is register service
Service *Service
// Id is register id
Id string
// ID is register id
ID string
// Type defines type of event
Type EventType
}

91
router/dns.go Normal file
View File

@@ -0,0 +1,91 @@
package router
import (
"fmt"
"net"
"strconv"
)
// NewRouter returns an initialized dns router
func NewRouter(opts ...Option) Router {
options := NewOptions(opts...)
return &dns{options}
}
type dns struct {
options Options
}
func (d *dns) Init(opts ...Option) error {
for _, o := range opts {
o(&d.options)
}
return nil
}
func (d *dns) Options() Options {
return d.options
}
func (d *dns) Table() Table {
return nil
}
func (d *dns) Close() error {
return nil
}
func (d *dns) Lookup(opts ...QueryOption) ([]Route, error) {
options := NewQuery(opts...)
// check to see if we have the port provided in the service, e.g. go-micro-srv-foo:8000
host, port, err := net.SplitHostPort(options.Service)
if err == nil {
// lookup the service using A records
ips, err := net.LookupHost(host)
if err != nil {
return nil, err
}
p, _ := strconv.Atoi(port)
// convert the ip addresses to routes
result := make([]Route, len(ips))
for i, ip := range ips {
result[i] = Route{
Service: options.Service,
Address: fmt.Sprintf("%s:%d", ip, uint16(p)),
}
}
return result, nil
}
// we didn't get the port so we'll lookup the service using SRV records. If we can't lookup the
// service using the SRV record, we return the error.
_, nodes, err := net.LookupSRV(options.Service, "tcp", d.options.Network)
if err != nil {
return nil, err
}
// convert the nodes (net services) to routes
result := make([]Route, len(nodes))
for i, n := range nodes {
result[i] = Route{
Service: options.Service,
Address: fmt.Sprintf("%s:%d", n.Target, n.Port),
Network: d.options.Network,
}
}
return result, nil
}
func (d *dns) Watch(opts ...WatchOption) (Watcher, error) {
return nil, nil
}
func (d *dns) Name() string {
return d.options.Name
}
func (d *dns) String() string {
return "dns"
}

View File

@@ -7,7 +7,7 @@ import (
var (
// DefaultRouter is the global default router
DefaultRouter Router
DefaultRouter Router = NewRouter()
// DefaultNetwork is default micro network
DefaultNetwork = "micro"
// ErrRouteNotFound is returned when no route was found in the routing table

View File

@@ -5,10 +5,8 @@ import (
"time"
)
var (
// ErrWatcherStopped is returned when routing table watcher has been stopped
ErrWatcherStopped = errors.New("watcher stopped")
)
// ErrWatcherStopped is returned when routing table watcher has been stopped
var ErrWatcherStopped = errors.New("watcher stopped")
// EventType defines routing table event
type EventType int
@@ -38,12 +36,12 @@ func (t EventType) String() string {
// Event is returned by a call to Next on the watcher.
type Event struct {
// Route is table route
Route Route
// Timestamp is event timestamp
Timestamp time.Time
// Id of the event
Id string
// Route is table route
Route Route
// Type defines type of event
Type EventType
}

View File

@@ -8,10 +8,8 @@ import (
"github.com/unistack-org/micro/v3/metadata"
)
var (
// ErrAlreadyExists error
ErrAlreadyExists = errors.New("already exists")
)
// ErrAlreadyExists error
var ErrAlreadyExists = errors.New("already exists")
// Runtime is a service runtime manager
type Runtime interface {

View File

@@ -5,10 +5,8 @@ import (
"errors"
)
var (
// ErrNoneAvailable is returned by select when no routes were provided to select from
ErrNoneAvailable = errors.New("none available")
)
// ErrNoneAvailable is returned by select when no routes were provided to select from
var ErrNoneAvailable = errors.New("none available")
// Selector selects a route from a pool
type Selector interface {

3
server/generate.go Normal file
View File

@@ -0,0 +1,3 @@
package server
//go:generate protoc -I./health -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:./health health/health.proto

View File

@@ -13,7 +13,7 @@ type rpcHandler struct {
endpoints []*register.Endpoint
}
func newRpcHandler(handler interface{}, opts ...HandlerOption) Handler {
func newRPCHandler(handler interface{}, opts ...HandlerOption) Handler {
options := NewHandlerOptions(opts...)
typ := reflect.TypeOf(handler)

82
server/health/health.go Normal file
View File

@@ -0,0 +1,82 @@
package health
import (
"context"
"github.com/unistack-org/micro/v3/codec"
"github.com/unistack-org/micro/v3/errors"
)
var _ HealthServer = &Handler{}
type Handler struct {
opts Options
}
type CheckFunc func(context.Context) error
type Option func(*Options)
type Options struct {
Version string
Name string
LiveChecks []CheckFunc
ReadyChecks []CheckFunc
}
func LiveChecks(fns ...CheckFunc) Option {
return func(o *Options) {
o.LiveChecks = append(o.LiveChecks, fns...)
}
}
func ReadyChecks(fns ...CheckFunc) Option {
return func(o *Options) {
o.ReadyChecks = append(o.ReadyChecks, fns...)
}
}
func Name(name string) Option {
return func(o *Options) {
o.Name = name
}
}
func Version(version string) Option {
return func(o *Options) {
o.Version = version
}
}
func NewHandler(opts ...Option) *Handler {
options := Options{}
for _, o := range opts {
o(&options)
}
return &Handler{opts: options}
}
func (h *Handler) Live(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error {
var err error
for _, fn := range h.opts.LiveChecks {
if err = fn(ctx); err != nil {
return errors.ServiceUnavailable(h.opts.Name, "%v", err)
}
}
return nil
}
func (h *Handler) Ready(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error {
var err error
for _, fn := range h.opts.ReadyChecks {
if err = fn(ctx); err != nil {
return errors.ServiceUnavailable(h.opts.Name, "%v", err)
}
}
return nil
}
func (h *Handler) Version(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error {
rsp.Data = []byte(h.opts.Version)
return nil
}

View File

@@ -0,0 +1,62 @@
syntax = "proto3";
package micro.server.health;
option go_package = "github.com/unistack-org/micro/v3/server/health;health";
import "api/annotations.proto";
import "openapiv2/annotations.proto";
import "codec/frame.proto";
service Health {
rpc Live(micro.codec.Frame) returns (micro.codec.Frame) {
option (micro.openapiv2.openapiv2_operation) = {
operation_id: "Live";
responses: {
key: "default";
value: {
description: "Error response";
schema: {
json_schema: {
ref: "micro.codec.Frame";
}
}
}
}
};
option (micro.api.http) = { get: "/live"; };
};
rpc Ready(micro.codec.Frame) returns (micro.codec.Frame) {
option (micro.openapiv2.openapiv2_operation) = {
operation_id: "Ready";
responses: {
key: "default";
value: {
description: "Error response";
schema: {
json_schema: {
ref: "micro.codec.Frame";
}
}
}
}
};
option (micro.api.http) = { get: "/ready"; };
};
rpc Version(micro.codec.Frame) returns (micro.codec.Frame) {
option (micro.openapiv2.openapiv2_operation) = {
operation_id: "Version";
responses: {
key: "default";
value: {
description: "Error response";
schema: {
json_schema: {
ref: "micro.codec.Frame";
}
}
}
}
};
option (micro.api.http) = { get: "/version"; };
};
};

View File

@@ -0,0 +1,38 @@
// Code generated by protoc-gen-micro
// source: health.proto
package health
import (
context "context"
api "github.com/unistack-org/micro/v3/api"
codec "github.com/unistack-org/micro/v3/codec"
)
func NewHealthEndpoints() []*api.Endpoint {
return []*api.Endpoint{
&api.Endpoint{
Name: "Health.Live",
Path: []string{"/live"},
Method: []string{"GET"},
Handler: "rpc",
},
&api.Endpoint{
Name: "Health.Ready",
Path: []string{"/ready"},
Method: []string{"GET"},
Handler: "rpc",
},
&api.Endpoint{
Name: "Health.Version",
Path: []string{"/version"},
Method: []string{"GET"},
Handler: "rpc",
},
}
}
type HealthServer interface {
Live(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error
Ready(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error
Version(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error
}

View File

@@ -0,0 +1,43 @@
// Code generated by protoc-gen-micro
// source: health.proto
package health
import (
context "context"
api "github.com/unistack-org/micro/v3/api"
codec "github.com/unistack-org/micro/v3/codec"
server "github.com/unistack-org/micro/v3/server"
)
type healthServer struct {
HealthServer
}
func (h *healthServer) Live(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error {
return h.HealthServer.Live(ctx, req, rsp)
}
func (h *healthServer) Ready(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error {
return h.HealthServer.Ready(ctx, req, rsp)
}
func (h *healthServer) Version(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error {
return h.HealthServer.Version(ctx, req, rsp)
}
func RegisterHealthServer(s server.Server, sh HealthServer, opts ...server.HandlerOption) error {
type health interface {
Live(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error
Ready(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error
Version(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error
}
type Health struct {
health
}
h := &healthServer{sh}
var nopts []server.HandlerOption
for _, endpoint := range NewHealthEndpoints() {
nopts = append(nopts, api.WithEndpoint(endpoint))
}
return s.Handle(s.NewHandler(&Health{h}, append(nopts, opts...)...))
}

View File

@@ -6,39 +6,32 @@ import (
"sync"
"time"
// cjson "github.com/unistack-org/micro-codec-json"
// cjsonrpc "github.com/unistack-org/micro-codec-jsonrpc"
// cproto "github.com/unistack-org/micro-codec-proto"
// cprotorpc "github.com/unistack-org/micro-codec-protorpc"
"github.com/unistack-org/micro/v3/broker"
"github.com/unistack-org/micro/v3/codec"
"github.com/unistack-org/micro/v3/logger"
"github.com/unistack-org/micro/v3/register"
maddr "github.com/unistack-org/micro/v3/util/addr"
mnet "github.com/unistack-org/micro/v3/util/net"
"github.com/unistack-org/micro/v3/util/rand"
)
var (
// DefaultCodecs will be used to encode/decode
DefaultCodecs = map[string]codec.Codec{
//"application/json": cjson.NewCodec,
//"application/json-rpc": cjsonrpc.NewCodec,
//"application/protobuf": cproto.NewCodec,
//"application/proto-rpc": cprotorpc.NewCodec,
"application/octet-stream": codec.NewCodec(),
}
)
// DefaultCodecs will be used to encode/decode
var DefaultCodecs = map[string]codec.Codec{
"application/octet-stream": codec.NewCodec(),
}
const (
defaultContentType = "application/json"
)
type noopServer struct {
opts Options
h Handler
wg *sync.WaitGroup
rsvc *register.Service
handlers map[string]Handler
subscribers map[*subscriber][]broker.Subscriber
exit chan chan error
wg *sync.WaitGroup
opts Options
sync.RWMutex
registered bool
started bool
@@ -82,8 +75,7 @@ func (n *noopServer) Subscribe(sb Subscriber) error {
sub, ok := sb.(*subscriber)
if !ok {
return fmt.Errorf("invalid subscriber: expected *subscriber")
}
if len(sub.handlers) == 0 {
} else if len(sub.handlers) == 0 {
return fmt.Errorf("invalid subscriber: no handler functions")
}
@@ -103,7 +95,7 @@ func (n *noopServer) Subscribe(sb Subscriber) error {
}
func (n *noopServer) NewHandler(h interface{}, opts ...HandlerOption) Handler {
return newRpcHandler(h, opts...)
return newRPCHandler(h, opts...)
}
func (n *noopServer) NewSubscriber(topic string, sb interface{}, opts ...SubscriberOption) Subscriber {
@@ -116,11 +108,12 @@ func (n *noopServer) Init(opts ...Option) error {
}
if n.handlers == nil {
n.handlers = make(map[string]Handler)
n.handlers = make(map[string]Handler, 1)
}
if n.subscribers == nil {
n.subscribers = make(map[*subscriber][]broker.Subscriber)
n.subscribers = make(map[*subscriber][]broker.Subscriber, 1)
}
if n.exit == nil {
n.exit = make(chan chan error)
}
@@ -158,23 +151,16 @@ func (n *noopServer) Register() error {
}
n.RLock()
// Maps are ordered randomly, sort the keys for consistency
var handlerList []string
for n, e := range n.handlers {
// Only advertise non internal handlers
if !e.Options().Internal {
handlerList = append(handlerList, n)
}
handlerList := make([]string, 0, len(n.handlers))
for n := range n.handlers {
handlerList = append(handlerList, n)
}
sort.Strings(handlerList)
var subscriberList []*subscriber
subscriberList := make([]*subscriber, 0, len(n.subscribers))
for e := range n.subscribers {
// Only advertise non internal subscribers
if !e.Options().Internal {
subscriberList = append(subscriberList, e)
}
subscriberList = append(subscriberList, e)
}
sort.Slice(subscriberList, func(i, j int) bool {
return subscriberList[i].topic > subscriberList[j].topic
@@ -190,7 +176,7 @@ func (n *noopServer) Register() error {
n.RUnlock()
service.Nodes[0].Metadata["protocol"] = "noop"
service.Nodes[0].Metadata["transport"] = "noop"
service.Nodes[0].Metadata["transport"] = service.Nodes[0].Metadata["protocol"]
service.Endpoints = endpoints
n.RLock()
@@ -199,7 +185,7 @@ func (n *noopServer) Register() error {
if !registered {
if config.Logger.V(logger.InfoLevel) {
config.Logger.Infof(n.opts.Context, "register [%s] Registering node: %s", config.Register.String(), service.Nodes[0].Id)
config.Logger.Infof(n.opts.Context, "register [%s] Registering node: %s", config.Register.String(), service.Nodes[0].ID)
}
}
@@ -218,26 +204,34 @@ func (n *noopServer) Register() error {
cx := config.Context
for sb := range n.subscribers {
handler := n.createSubHandler(sb, config)
var opts []broker.SubscribeOption
if queue := sb.Options().Queue; len(queue) > 0 {
opts = append(opts, broker.SubscribeGroup(queue))
}
var sub broker.Subscriber
for sb := range n.subscribers {
if sb.Options().Context != nil {
cx = sb.Options().Context
}
opts = append(opts, broker.SubscribeContext(cx), broker.SubscribeAutoAck(sb.Options().AutoAck))
opts := []broker.SubscribeOption{broker.SubscribeContext(cx), broker.SubscribeAutoAck(sb.Options().AutoAck)}
if queue := sb.Options().Queue; len(queue) > 0 {
opts = append(opts, broker.SubscribeGroup(queue))
}
if sb.Options().Batch {
// batch processing handler
sub, err = config.Broker.BatchSubscribe(cx, sb.Topic(), n.newBatchSubHandler(sb, config), opts...)
} else {
// single processing handler
sub, err = config.Broker.Subscribe(cx, sb.Topic(), n.newSubHandler(sb, config), opts...)
}
if err != nil {
return err
}
if config.Logger.V(logger.InfoLevel) {
config.Logger.Infof(n.opts.Context, "subscribing to topic: %s", sb.Topic())
}
sub, err := config.Broker.Subscribe(cx, sb.Topic(), handler, opts...)
if err != nil {
return err
}
n.subscribers[sb] = []broker.Subscriber{sub}
}
@@ -262,7 +256,7 @@ func (n *noopServer) Deregister() error {
}
if config.Logger.V(logger.InfoLevel) {
config.Logger.Infof(n.opts.Context, "deregistering node: %s", service.Nodes[0].Id)
config.Logger.Infof(n.opts.Context, "deregistering node: %s", service.Nodes[0].ID)
}
if err := DefaultDeregisterFunc(service, config); err != nil {
@@ -319,9 +313,22 @@ func (n *noopServer) Start() error {
config := n.Options()
n.RUnlock()
// use 127.0.0.1 to avoid scan of all network interfaces
addr, err := maddr.Extract("127.0.0.1")
if err != nil {
return err
}
var rng rand.Rand
i := rng.Intn(20000)
// set addr with port
addr = mnet.HostPort(addr, 10000+i)
config.Address = addr
if config.Logger.V(logger.InfoLevel) {
config.Logger.Infof(n.opts.Context, "server [noop] Listening on %s", config.Address)
}
n.Lock()
if len(config.Advertise) == 0 {
config.Advertise = config.Address
@@ -344,9 +351,10 @@ func (n *noopServer) Start() error {
}
// use RegisterCheck func before register
// nolint: nestif
if err := config.RegisterCheck(config.Context); err != nil {
if config.Logger.V(logger.ErrorLevel) {
config.Logger.Errorf(n.opts.Context, "server %s-%s register check error: %s", config.Name, config.Id, err)
config.Logger.Errorf(n.opts.Context, "server %s-%s register check error: %s", config.Name, config.ID, err)
}
} else {
// announce self to the world
@@ -378,25 +386,26 @@ func (n *noopServer) Start() error {
registered := n.registered
n.RUnlock()
rerr := config.RegisterCheck(config.Context)
// nolint: nestif
if rerr != nil && registered {
if config.Logger.V(logger.ErrorLevel) {
config.Logger.Errorf(n.opts.Context, "server %s-%s register check error: %s, deregister it", config.Name, config.Id, rerr)
config.Logger.Errorf(n.opts.Context, "server %s-%s register check error: %s, deregister it", config.Name, config.ID, rerr)
}
// deregister self in case of error
if err := n.Deregister(); err != nil {
if config.Logger.V(logger.ErrorLevel) {
config.Logger.Errorf(n.opts.Context, "server %s-%s deregister error: %s", config.Name, config.Id, err)
config.Logger.Errorf(n.opts.Context, "server %s-%s deregister error: %s", config.Name, config.ID, err)
}
}
} else if rerr != nil && !registered {
if config.Logger.V(logger.ErrorLevel) {
config.Logger.Errorf(n.opts.Context, "server %s-%s register check error: %s", config.Name, config.Id, rerr)
config.Logger.Errorf(n.opts.Context, "server %s-%s register check error: %s", config.Name, config.ID, rerr)
}
continue
}
if err := n.Register(); err != nil {
if config.Logger.V(logger.ErrorLevel) {
config.Logger.Errorf(n.opts.Context, "server %s-%s register error: %s", config.Name, config.Id, err)
config.Logger.Errorf(n.opts.Context, "server %s-%s register error: %s", config.Name, config.ID, err)
}
}
// wait for exit

106
server/noop_test.go Normal file
View File

@@ -0,0 +1,106 @@
package server_test
import (
"context"
"fmt"
"testing"
"github.com/unistack-org/micro/v3/broker"
"github.com/unistack-org/micro/v3/client"
"github.com/unistack-org/micro/v3/codec"
"github.com/unistack-org/micro/v3/metadata"
"github.com/unistack-org/micro/v3/server"
)
type TestHandler struct {
t *testing.T
}
type TestMessage struct {
Name string
}
var (
numMsg int = 8
)
func (h *TestHandler) SingleSubHandler(ctx context.Context, msg *codec.Frame) error {
//fmt.Printf("msg %s\n", msg.Data)
return nil
}
func (h *TestHandler) BatchSubHandler(ctxs []context.Context, msgs []*codec.Frame) error {
if len(msgs) != 8 {
h.t.Fatal("invalid number of messages received")
}
for idx := 0; idx < len(msgs); idx++ {
md, _ := metadata.FromIncomingContext(ctxs[idx])
_ = md
// fmt.Printf("msg md %v\n", md)
}
return nil
}
func TestNoopSub(t *testing.T) {
ctx := context.Background()
b := broker.NewBroker()
if err := b.Init(); err != nil {
t.Fatal(err)
}
if err := b.Connect(ctx); err != nil {
t.Fatal(err)
}
s := server.NewServer(
server.Broker(b),
server.Codec("application/octet-stream", codec.NewCodec()),
)
if err := s.Init(); err != nil {
t.Fatal(err)
}
c := client.NewClient(
client.Broker(b),
client.Codec("application/octet-stream", codec.NewCodec()),
client.ContentType("application/octet-stream"),
)
if err := c.Init(); err != nil {
t.Fatal(err)
}
h := &TestHandler{t: t}
if err := s.Subscribe(s.NewSubscriber("single_topic", h.SingleSubHandler,
server.SubscriberQueue("queue"),
)); err != nil {
t.Fatal(err)
}
if err := s.Subscribe(s.NewSubscriber("batch_topic", h.BatchSubHandler,
server.SubscriberQueue("queue"),
server.SubscriberBatch(true),
)); err != nil {
t.Fatal(err)
}
if err := s.Start(); err != nil {
t.Fatal(err)
}
msgs := make([]client.Message, 0, 8)
for i := 0; i < 8; i++ {
msgs = append(msgs, c.NewMessage("batch_topic", &codec.Frame{Data: []byte(fmt.Sprintf(`{"name": "test_name %d"}`, i))}))
}
if err := c.BatchPublish(ctx, msgs); err != nil {
t.Fatal(err)
}
defer func() {
if err := s.Stop(); err != nil {
t.Fatal(err)
}
}()
}

View File

@@ -57,8 +57,8 @@ type Options struct {
RegisterCheck func(context.Context) error
// Codecs map to handle content-type
Codecs map[string]codec.Codec
// Id holds the id of the server
Id string
// ID holds the id of the server
ID string
// Namespace for te server
Namespace string
// Name holds the server name
@@ -71,6 +71,8 @@ type Options struct {
Version string
// SubWrappers holds the server subscribe wrappers
SubWrappers []SubscriberWrapper
// BatchSubWrappers holds the server batch subscribe wrappers
BatchSubWrappers []BatchSubscriberWrapper
// HdlrWrappers holds the handler wrappers
HdlrWrappers []HandlerWrapper
// RegisterAttempts holds the number of register attempts before error
@@ -104,7 +106,7 @@ func NewOptions(opts ...Option) Options {
Address: DefaultAddress,
Name: DefaultName,
Version: DefaultVersion,
Id: DefaultId,
ID: DefaultID,
Namespace: DefaultNamespace,
}
@@ -143,10 +145,10 @@ func Meter(m meter.Meter) Option {
}
}
// Id unique server id
func Id(id string) Option {
// ID unique server id
func ID(id string) Option {
return func(o *Options) {
o.Id = id
o.ID = id
}
}
@@ -302,6 +304,13 @@ func WrapSubscriber(w SubscriberWrapper) Option {
}
}
// WrapBatchSubscriber adds a batch subscriber Wrapper to a list of options passed into the server
func WrapBatchSubscriber(w BatchSubscriberWrapper) Option {
return func(o *Options) {
o.BatchSubWrappers = append(o.BatchSubWrappers, w)
}
}
// MaxConn specifies maximum number of max simultaneous connections to server
func MaxConn(n int) Option {
return func(o *Options) {
@@ -325,8 +334,6 @@ type HandlerOptions struct {
Context context.Context
// Metadata for hondler
Metadata map[string]metadata.Metadata
// Internal flag limits exporting to other nodes via register
Internal bool
}
// NewHandlerOptions creates new HandlerOptions
@@ -354,10 +361,14 @@ type SubscriberOptions struct {
Queue string
// AutoAck flag for auto ack messages after processing
AutoAck bool
// Internal flag limit exporting info via register
Internal bool
// BodyOnly flag specifies that message without headers
BodyOnly bool
// Batch flag specifies that message processed in batches
Batch bool
// BatchSize flag specifies max size of batch
BatchSize int
// BatchWait flag specifies max wait time for batch filling
BatchWait time.Duration
}
// NewSubscriberOptions create new SubscriberOptions
@@ -382,23 +393,6 @@ func EndpointMetadata(name string, md metadata.Metadata) HandlerOption {
}
}
// InternalHandler options specifies that a handler is not advertised
// to the discovery system. In the future this may also limit request
// to the internal network or authorised user.
func InternalHandler(b bool) HandlerOption {
return func(o *HandlerOptions) {
o.Internal = b
}
}
// InternalSubscriber options specifies that a subscriber is not advertised
// to the discovery system.
func InternalSubscriber(b bool) SubscriberOption {
return func(o *SubscriberOptions) {
o.Internal = b
}
}
// DisableAutoAck will disable auto acking of messages
// after they have been handled.
func DisableAutoAck() SubscriberOption {
@@ -434,3 +428,32 @@ func SubscriberContext(ctx context.Context) SubscriberOption {
o.Context = ctx
}
}
// SubscriberAck control auto ack processing for handler
func SubscriberAck(b bool) SubscriberOption {
return func(o *SubscriberOptions) {
o.AutoAck = b
}
}
// SubscriberBatch control batch processing for handler
func SubscriberBatch(b bool) SubscriberOption {
return func(o *SubscriberOptions) {
o.Batch = b
}
}
// SubscriberBatchSize control batch filling size for handler
// Batch filling max waiting time controlled by SubscriberBatchWait
func SubscriberBatchSize(n int) SubscriberOption {
return func(o *SubscriberOptions) {
o.BatchSize = n
}
}
// SubscriberBatchWait control batch filling wait time for handler
func SubscriberBatchWait(td time.Duration) SubscriberOption {
return func(o *SubscriberOptions) {
o.BatchWait = td
}
}

View File

@@ -72,7 +72,7 @@ func NewRegisterService(s Server) (*register.Service, error) {
}
node := &register.Node{
Id: opts.Name + "-" + opts.Id,
ID: opts.Name + "-" + opts.ID,
Address: net.JoinHostPort(addr, port),
}
node.Metadata = metadata.Copy(opts.Metadata)

View File

@@ -11,20 +11,18 @@ import (
"github.com/unistack-org/micro/v3/register"
)
var (
// DefaultServer default server
DefaultServer Server = NewServer()
)
// DefaultServer default server
var DefaultServer Server = NewServer()
var (
// DefaultAddress will be used if no address passed
DefaultAddress = ":0"
// DefaultAddress will be used if no address passed, use secure localhost
DefaultAddress = "127.0.0.1:0"
// DefaultName will be used if no name passed
DefaultName = "server"
// DefaultVersion will be used if no version passed
DefaultVersion = "latest"
// DefaultId will be used if no id passed
DefaultId = uuid.New().String()
// DefaultID will be used if no id passed
DefaultID = uuid.New().String()
// DefaultRegisterCheck holds func that run before register server
DefaultRegisterCheck = func(context.Context) error { return nil }
// DefaultRegisterInterval holds interval for register

View File

@@ -18,14 +18,13 @@ import (
)
const (
subSig = "func(context.Context, interface{}) error"
subSig = "func(context.Context, interface{}) error"
batchSubSig = "func([]context.Context, []interface{}) error"
)
var (
// Precompute the reflect type for error. Can't use error directly
// because Typeof takes an empty interface value. This is annoying.
typeOfError = reflect.TypeOf((*error)(nil)).Elem()
)
// Precompute the reflect type for error. Can't use error directly
// because Typeof takes an empty interface value. This is annoying.
var typeOfError = reflect.TypeOf((*error)(nil)).Elem()
type handler struct {
reqType reflect.Type
@@ -59,42 +58,49 @@ func isExportedOrBuiltinType(t reflect.Type) bool {
return isExported(t.Name()) || t.PkgPath() == ""
}
// ValidateSubscriber func
// ValidateSubscriber func signature
func ValidateSubscriber(sub Subscriber) error {
typ := reflect.TypeOf(sub.Subscriber())
var argType reflect.Type
if typ.Kind() == reflect.Func {
switch typ.Kind() {
case reflect.Func:
name := "Func"
switch typ.NumIn() {
case 2:
argType = typ.In(1)
if sub.Options().Batch {
if argType.Kind() != reflect.Slice {
return fmt.Errorf("subscriber %v dont have required signature %s", name, batchSubSig)
}
if strings.Compare(fmt.Sprintf("%s", argType), "[]interface{}") == 0 {
return fmt.Errorf("subscriber %v dont have required signaure %s", name, batchSubSig)
}
}
default:
return fmt.Errorf("subscriber %v takes wrong number of args: %v required signature %s", name, typ.NumIn(), subSig)
return fmt.Errorf("subscriber %v takes wrong number of args: %v required signature %s or %s", name, typ.NumIn(), subSig, batchSubSig)
}
if !isExportedOrBuiltinType(argType) {
return fmt.Errorf("subscriber %v argument type not exported: %v", name, argType)
}
if typ.NumOut() != 1 {
return fmt.Errorf("subscriber %v has wrong number of outs: %v require signature %s",
name, typ.NumOut(), subSig)
return fmt.Errorf("subscriber %v has wrong number of return values: %v require signature %s or %s",
name, typ.NumOut(), subSig, batchSubSig)
}
if returnType := typ.Out(0); returnType != typeOfError {
return fmt.Errorf("subscriber %v returns %v not error", name, returnType.String())
}
} else {
default:
hdlr := reflect.ValueOf(sub.Subscriber())
name := reflect.Indirect(hdlr).Type().Name()
for m := 0; m < typ.NumMethod(); m++ {
method := typ.Method(m)
switch method.Type.NumIn() {
case 3:
argType = method.Type.In(2)
default:
return fmt.Errorf("subscriber %v.%v takes wrong number of args: %v required signature %s",
name, method.Name, method.Type.NumIn(), subSig)
return fmt.Errorf("subscriber %v.%v takes wrong number of args: %v required signature %s or %s",
name, method.Name, method.Type.NumIn(), subSig, batchSubSig)
}
if !isExportedOrBuiltinType(argType) {
@@ -102,8 +108,8 @@ func ValidateSubscriber(sub Subscriber) error {
}
if method.Type.NumOut() != 1 {
return fmt.Errorf(
"subscriber %v.%v has wrong number of outs: %v require signature %s",
name, method.Name, method.Type.NumOut(), subSig)
"subscriber %v.%v has wrong number of return values: %v require signature %s or %s",
name, method.Name, method.Type.NumOut(), subSig, batchSubSig)
}
if returnType := method.Type.Out(0); returnType != typeOfError {
return fmt.Errorf("subscriber %v.%v returns %v not error", name, method.Name, returnType.String())
@@ -184,7 +190,125 @@ func newSubscriber(topic string, sub interface{}, opts ...SubscriberOption) Subs
}
//nolint:gocyclo
func (n *noopServer) createSubHandler(sb *subscriber, opts Options) broker.Handler {
func (n *noopServer) newBatchSubHandler(sb *subscriber, opts Options) broker.BatchHandler {
return func(ps broker.Events) (err error) {
defer func() {
if r := recover(); r != nil {
n.RLock()
config := n.opts
n.RUnlock()
if config.Logger.V(logger.ErrorLevel) {
config.Logger.Error(n.opts.Context, "panic recovered: ", r)
config.Logger.Error(n.opts.Context, string(debug.Stack()))
}
err = errors.InternalServerError(n.opts.Name+".subscriber", "panic recovered: %v", r)
}
}()
msgs := make([]Message, 0, len(ps))
ctxs := make([]context.Context, 0, len(ps))
for _, p := range ps {
msg := p.Message()
// if we don't have headers, create empty map
if msg.Header == nil {
msg.Header = metadata.New(2)
}
ct, _ := msg.Header.Get(metadata.HeaderContentType)
if len(ct) == 0 {
msg.Header.Set(metadata.HeaderContentType, defaultContentType)
ct = defaultContentType
}
hdr := metadata.Copy(msg.Header)
topic, _ := msg.Header.Get(metadata.HeaderTopic)
ctxs = append(ctxs, metadata.NewIncomingContext(sb.opts.Context, hdr))
msgs = append(msgs, &rpcMessage{
topic: topic,
contentType: ct,
header: msg.Header,
body: msg.Body,
})
}
results := make(chan error, len(sb.handlers))
for i := 0; i < len(sb.handlers); i++ {
handler := sb.handlers[i]
var req reflect.Value
switch handler.reqType.Kind() {
case reflect.Ptr:
req = reflect.New(handler.reqType.Elem())
default:
req = reflect.New(handler.reqType.Elem()).Elem()
}
reqType := handler.reqType
for _, msg := range msgs {
cf, err := n.newCodec(msg.ContentType())
if err != nil {
return err
}
rb := reflect.New(req.Type().Elem())
if err = cf.ReadBody(bytes.NewReader(msg.Body()), rb.Interface()); err != nil {
return err
}
msg.(*rpcMessage).codec = cf
msg.(*rpcMessage).payload = rb.Interface()
}
fn := func(ctxs []context.Context, ms []Message) error {
var vals []reflect.Value
if sb.typ.Kind() != reflect.Func {
vals = append(vals, sb.rcvr)
}
if handler.ctxType != nil {
vals = append(vals, reflect.ValueOf(ctxs))
}
payloads := reflect.MakeSlice(reqType, 0, len(ms))
for _, m := range ms {
payloads = reflect.Append(payloads, reflect.ValueOf(m.Payload()))
}
vals = append(vals, payloads)
returnValues := handler.method.Call(vals)
if rerr := returnValues[0].Interface(); rerr != nil {
return rerr.(error)
}
return nil
}
for i := len(opts.BatchSubWrappers); i > 0; i-- {
fn = opts.BatchSubWrappers[i-1](fn)
}
if n.wg != nil {
n.wg.Add(1)
}
go func() {
if n.wg != nil {
defer n.wg.Done()
}
results <- fn(ctxs, msgs)
}()
}
var errors []string
for i := 0; i < len(sb.handlers); i++ {
if rerr := <-results; rerr != nil {
errors = append(errors, rerr.Error())
}
}
if len(errors) > 0 {
err = fmt.Errorf("subscriber error: %s", strings.Join(errors, "\n"))
}
return err
}
}
//nolint:gocyclo
func (n *noopServer) newSubHandler(sb *subscriber, opts Options) broker.Handler {
return func(p broker.Event) (err error) {
defer func() {
if r := recover(); r != nil {
@@ -202,12 +326,12 @@ func (n *noopServer) createSubHandler(sb *subscriber, opts Options) broker.Handl
msg := p.Message()
// if we don't have headers, create empty map
if msg.Header == nil {
msg.Header = make(map[string]string)
msg.Header = metadata.New(2)
}
ct := msg.Header["Content-Type"]
if len(ct) == 0 {
msg.Header["Content-Type"] = defaultContentType
msg.Header.Set(metadata.HeaderContentType, defaultContentType)
ct = defaultContentType
}
cf, err := n.newCodec(ct)
@@ -215,12 +339,12 @@ func (n *noopServer) createSubHandler(sb *subscriber, opts Options) broker.Handl
return err
}
hdr := make(map[string]string, len(msg.Header))
hdr := metadata.New(len(msg.Header))
for k, v := range msg.Header {
if k == "Content-Type" {
continue
}
hdr[k] = v
hdr.Set(k, v)
}
ctx := metadata.NewIncomingContext(sb.opts.Context, hdr)
@@ -276,14 +400,14 @@ func (n *noopServer) createSubHandler(sb *subscriber, opts Options) broker.Handl
if n.wg != nil {
defer n.wg.Done()
}
err := fn(ctx, &rpcMessage{
cerr := fn(ctx, &rpcMessage{
topic: sb.topic,
contentType: ct,
payload: req.Interface(),
header: msg.Header,
body: msg.Body,
})
results <- err
results <- cerr
}()
}
var errors []string
@@ -295,7 +419,6 @@ func (n *noopServer) createSubHandler(sb *subscriber, opts Options) broker.Handl
if len(errors) > 0 {
err = fmt.Errorf("subscriber error: %s", strings.Join(errors, "\n"))
}
return err
}
}

View File

@@ -14,12 +14,20 @@ type HandlerFunc func(ctx context.Context, req Request, rsp interface{}) error
// publication message.
type SubscriberFunc func(ctx context.Context, msg Message) error
// BatchSubscriberFunc represents a single method of a subscriber. It's used primarily
// for the wrappers. What's handed to the actual method is the concrete
// publication message. This func used by batch subscribers
type BatchSubscriberFunc func(ctxs []context.Context, msgs []Message) error
// HandlerWrapper wraps the HandlerFunc and returns the equivalent
type HandlerWrapper func(HandlerFunc) HandlerFunc
// SubscriberWrapper wraps the SubscriberFunc and returns the equivalent
type SubscriberWrapper func(SubscriberFunc) SubscriberFunc
// BatchSubscriberWrapper wraps the SubscriberFunc and returns the equivalent
type BatchSubscriberWrapper func(BatchSubscriberFunc) BatchSubscriberFunc
// StreamWrapper wraps a Stream interface and returns the equivalent.
// Because streams exist for the lifetime of a method invocation this
// is a convenient way to wrap a Stream as its in use for trace, monitoring,

View File

@@ -103,48 +103,48 @@ func (s *service) Init(opts ...Option) error {
// skip config as the struct not passed
continue
}
if err = cfg.Init(config.Context(s.opts.Context)); err != nil {
if err = cfg.Init(config.Context(cfg.Options().Context)); err != nil {
return err
}
if err = cfg.Load(s.opts.Context); err != nil {
if err = cfg.Load(cfg.Options().Context); err != nil {
return err
}
}
for _, log := range s.opts.Loggers {
if err = log.Init(logger.WithContext(s.opts.Context)); err != nil {
if err = log.Init(logger.WithContext(log.Options().Context)); err != nil {
return err
}
}
for _, reg := range s.opts.Registers {
if err = reg.Init(register.Context(s.opts.Context)); err != nil {
if err = reg.Init(register.Context(reg.Options().Context)); err != nil {
return err
}
}
for _, brk := range s.opts.Brokers {
if err = brk.Init(broker.Context(s.opts.Context)); err != nil {
if err = brk.Init(broker.Context(brk.Options().Context)); err != nil {
return err
}
}
for _, str := range s.opts.Stores {
if err = str.Init(store.Context(s.opts.Context)); err != nil {
if err = str.Init(store.Context(str.Options().Context)); err != nil {
return err
}
}
for _, srv := range s.opts.Servers {
if err = srv.Init(server.Context(s.opts.Context)); err != nil {
if err = srv.Init(server.Context(srv.Options().Context)); err != nil {
return err
}
}
for _, cli := range s.opts.Clients {
if err = cli.Init(client.Context(s.opts.Context)); err != nil {
if err = cli.Init(client.Context(cli.Options().Context)); err != nil {
return err
}
}
@@ -162,7 +162,6 @@ func (s *service) Broker(names ...string) broker.Broker {
idx = getNameIndex(names[0], s.opts.Brokers)
}
return s.opts.Brokers[idx]
}
func (s *service) Tracer(names ...string) tracer.Tracer {

View File

@@ -28,24 +28,14 @@ func (m *memoryStore) Disconnect(ctx context.Context) error {
}
type memoryStore struct {
opts Options
store *cache.Cache
opts Options
}
func (m *memoryStore) key(prefix, key string) string {
return filepath.Join(prefix, key)
}
func (m *memoryStore) prefix(database, table string) string {
if len(database) == 0 {
database = m.opts.Database
}
if len(table) == 0 {
table = m.opts.Table
}
return filepath.Join(database, table)
}
func (m *memoryStore) exists(prefix, key string) error {
key = m.key(prefix, key)
@@ -80,15 +70,17 @@ func (m *memoryStore) delete(prefix, key string) {
func (m *memoryStore) list(prefix string, limit, offset uint) []string {
allItems := m.store.Items()
allKeys := make([]string, len(allItems))
i := 0
allKeys := make([]string, 0, len(allItems))
for k := range allItems {
if !strings.HasPrefix(k, prefix+"/") {
if !strings.HasPrefix(k, prefix) {
continue
}
allKeys[i] = strings.TrimPrefix(k, prefix+"/")
i++
k = strings.TrimPrefix(k, prefix)
if k[0] == '/' {
k = k[1:]
}
allKeys = append(allKeys, k)
}
if limit != 0 || offset != 0 {
@@ -107,7 +99,6 @@ func (m *memoryStore) list(prefix string, limit, offset uint) []string {
}
return allKeys[offset:end]
}
return allKeys
}
@@ -127,37 +118,48 @@ func (m *memoryStore) Name() string {
}
func (m *memoryStore) Exists(ctx context.Context, key string, opts ...ExistsOption) error {
prefix := m.prefix(m.opts.Database, m.opts.Table)
return m.exists(prefix, key)
options := NewExistsOptions(opts...)
if options.Namespace == "" {
options.Namespace = m.opts.Namespace
}
return m.exists(options.Namespace, key)
}
func (m *memoryStore) Read(ctx context.Context, key string, val interface{}, opts ...ReadOption) error {
readOpts := NewReadOptions(opts...)
prefix := m.prefix(readOpts.Database, readOpts.Table)
return m.get(prefix, key, val)
options := NewReadOptions(opts...)
if options.Namespace == "" {
options.Namespace = m.opts.Namespace
}
return m.get(options.Namespace, key, val)
}
func (m *memoryStore) Write(ctx context.Context, key string, val interface{}, opts ...WriteOption) error {
writeOpts := NewWriteOptions(opts...)
options := NewWriteOptions(opts...)
if options.Namespace == "" {
options.Namespace = m.opts.Namespace
}
if options.TTL == 0 {
options.TTL = cache.NoExpiration
}
prefix := m.prefix(writeOpts.Database, writeOpts.Table)
key = m.key(prefix, key)
key = m.key(options.Namespace, key)
buf, err := m.opts.Codec.Marshal(val)
if err != nil {
return err
}
m.store.Set(key, buf, writeOpts.TTL)
m.store.Set(key, buf, options.TTL)
return nil
}
func (m *memoryStore) Delete(ctx context.Context, key string, opts ...DeleteOption) error {
deleteOptions := NewDeleteOptions(opts...)
options := NewDeleteOptions(opts...)
if options.Namespace == "" {
options.Namespace = m.opts.Namespace
}
prefix := m.prefix(deleteOptions.Database, deleteOptions.Table)
m.delete(prefix, key)
m.delete(options.Namespace, key)
return nil
}
@@ -166,25 +168,27 @@ func (m *memoryStore) Options() Options {
}
func (m *memoryStore) List(ctx context.Context, opts ...ListOption) ([]string, error) {
listOptions := NewListOptions(opts...)
options := NewListOptions(opts...)
if options.Namespace == "" {
options.Namespace = m.opts.Namespace
}
prefix := m.prefix(listOptions.Database, listOptions.Table)
keys := m.list(prefix, listOptions.Limit, listOptions.Offset)
keys := m.list(options.Namespace, options.Limit, options.Offset)
if len(listOptions.Prefix) > 0 {
if len(options.Prefix) > 0 {
var prefixKeys []string
for _, k := range keys {
if strings.HasPrefix(k, listOptions.Prefix) {
if strings.HasPrefix(k, options.Prefix) {
prefixKeys = append(prefixKeys, k)
}
}
keys = prefixKeys
}
if len(listOptions.Suffix) > 0 {
if len(options.Suffix) > 0 {
var suffixKeys []string
for _, k := range keys {
if strings.HasSuffix(k, listOptions.Suffix) {
if strings.HasSuffix(k, options.Suffix) {
suffixKeys = append(suffixKeys, k)
}
}

View File

@@ -9,11 +9,11 @@ import (
)
func TestMemoryReInit(t *testing.T) {
s := store.NewStore(store.Table("aaa"))
if err := s.Init(store.Table("")); err != nil {
s := store.NewStore(store.Namespace("aaa"))
if err := s.Init(store.Namespace("")); err != nil {
t.Fatal(err)
}
if len(s.Options().Table) > 0 {
if len(s.Options().Namespace) > 0 {
t.Error("Init didn't reinitialise the store")
}
}
@@ -28,7 +28,7 @@ func TestMemoryBasic(t *testing.T) {
func TestMemoryPrefix(t *testing.T) {
s := store.NewStore()
if err := s.Init(store.Table("some-prefix")); err != nil {
if err := s.Init(store.Namespace("some-prefix")); err != nil {
t.Fatal(err)
}
basictest(s, t)
@@ -36,7 +36,7 @@ func TestMemoryPrefix(t *testing.T) {
func TestMemoryNamespace(t *testing.T) {
s := store.NewStore()
if err := s.Init(store.Database("some-namespace")); err != nil {
if err := s.Init(store.Namespace("some-namespace")); err != nil {
t.Fatal(err)
}
basictest(s, t)
@@ -44,7 +44,7 @@ func TestMemoryNamespace(t *testing.T) {
func TestMemoryNamespacePrefix(t *testing.T) {
s := store.NewStore()
if err := s.Init(store.Table("some-prefix"), store.Database("some-namespace")); err != nil {
if err := s.Init(store.Namespace("some-namespace")); err != nil {
t.Fatal(err)
}
basictest(s, t)

View File

@@ -28,13 +28,12 @@ type Options struct {
TLSConfig *tls.Config
// Name specifies store name
Name string
// Database specifies store database
Database string
// Table specifies store table
Table string
// Nodes contains store address
// TODO: replace with Addrs
Nodes []string
// Namespace of the records
Namespace string
// Addrs contains store address
Addrs []string
//Wrappers store wrapper that called before actual functions
//Wrappers []Wrapper
}
// NewOptions creates options struct
@@ -90,13 +89,20 @@ func Meter(m meter.Meter) Option {
}
}
// Name the name
// Name the name of the store
func Name(n string) Option {
return func(o *Options) {
o.Name = n
}
}
// Namespace sets namespace of the store
func Namespace(ns string) Option {
return func(o *Options) {
o.Namespace = ns
}
}
// Tracer sets the tracer
func Tracer(t tracer.Tracer) Option {
return func(o *Options) {
@@ -104,27 +110,21 @@ func Tracer(t tracer.Tracer) Option {
}
}
// Nodes contains the addresses or other connection information of the backing storage.
// Addrs contains the addresses or other connection information of the backing storage.
// For example, an etcd implementation would contain the nodes of the cluster.
// A SQL implementation could contain one or more connection strings.
func Nodes(a ...string) Option {
func Addrs(addrs ...string) Option {
return func(o *Options) {
o.Nodes = a
o.Addrs = addrs
}
}
// Database allows multiple isolated stores to be kept in one backend, if supported.
func Database(db string) Option {
return func(o *Options) {
o.Database = db
}
}
// Table is analag for a table in database backends or a key prefix in KV backends
func Table(t string) Option {
return func(o *Options) {
o.Table = t
}
// ReadOptions configures an individual Read operation
type ReadOptions struct {
// Context holds external options
Context context.Context
// Namespace holds namespace
Namespace string
}
// NewReadOptions fills ReadOptions struct with opts slice
@@ -136,29 +136,35 @@ func NewReadOptions(opts ...ReadOption) ReadOptions {
return options
}
// ReadOptions configures an individual Read operation
type ReadOptions struct {
// Context holds external options
Context context.Context
// Database holds the database name
Database string
// Table holds table name
Table string
// Namespace holds namespace
Namespace string
}
// ReadOption sets values in ReadOptions
type ReadOption func(r *ReadOptions)
// ReadFrom the database and table
func ReadFrom(database, table string) ReadOption {
return func(r *ReadOptions) {
r.Database = database
r.Table = table
// ReadContext pass context.Context to ReadOptions
func ReadContext(ctx context.Context) ReadOption {
return func(o *ReadOptions) {
o.Context = ctx
}
}
// ReadNamespace pass namespace to ReadOptions
func ReadNamespace(ns string) ReadOption {
return func(o *ReadOptions) {
o.Namespace = ns
}
}
// WriteOptions configures an individual Write operation
type WriteOptions struct {
// Context holds external options
Context context.Context
// Metadata contains additional metadata
Metadata metadata.Metadata
// Namespace holds namespace
Namespace string
// TTL specifies key TTL
TTL time.Duration
}
// NewWriteOptions fills WriteOptions struct with opts slice
func NewWriteOptions(opts ...WriteOption) WriteOptions {
options := WriteOptions{}
@@ -168,47 +174,45 @@ func NewWriteOptions(opts ...WriteOption) WriteOptions {
return options
}
// WriteOptions configures an individual Write operation
type WriteOptions struct {
// Context holds external options
Context context.Context
// Metadata contains additional metadata
Metadata metadata.Metadata
// Database holds database name
Database string
// Table holds table name
Table string
// Namespace holds namespace
Namespace string
// TTL specifies key TTL
TTL time.Duration
}
// WriteOption sets values in WriteOptions
type WriteOption func(w *WriteOptions)
// WriteTo the database and table
func WriteTo(database, table string) WriteOption {
return func(w *WriteOptions) {
w.Database = database
w.Table = table
}
}
// WriteTTL is the time the record expires
func WriteTTL(d time.Duration) WriteOption {
return func(w *WriteOptions) {
w.TTL = d
// WriteContext pass context.Context to wirte options
func WriteContext(ctx context.Context) WriteOption {
return func(o *WriteOptions) {
o.Context = ctx
}
}
// WriteMetadata add metadata.Metadata
func WriteMetadata(md metadata.Metadata) WriteOption {
return func(w *WriteOptions) {
w.Metadata = metadata.Copy(md)
return func(o *WriteOptions) {
o.Metadata = metadata.Copy(md)
}
}
// WriteTTL is the time the record expires
func WriteTTL(d time.Duration) WriteOption {
return func(o *WriteOptions) {
o.TTL = d
}
}
// WriteNamespace pass namespace to write options
func WriteNamespace(ns string) WriteOption {
return func(o *WriteOptions) {
o.Namespace = ns
}
}
// DeleteOptions configures an individual Delete operation
type DeleteOptions struct {
// Context holds external options
Context context.Context
// Namespace holds namespace
Namespace string
}
// NewDeleteOptions fills DeleteOptions struct with opts slice
func NewDeleteOptions(opts ...DeleteOption) DeleteOptions {
options := DeleteOptions{}
@@ -218,29 +222,33 @@ func NewDeleteOptions(opts ...DeleteOption) DeleteOptions {
return options
}
// DeleteOptions configures an individual Delete operation
type DeleteOptions struct {
// Context holds external options
Context context.Context
// Database holds database name
Database string
// Table holds table name
Table string
// Namespace holds namespace
Namespace string
}
// DeleteOption sets values in DeleteOptions
type DeleteOption func(d *DeleteOptions)
// DeleteFrom the database and table
func DeleteFrom(database, table string) DeleteOption {
return func(d *DeleteOptions) {
d.Database = database
d.Table = table
// DeleteContext pass context.Context to delete options
func DeleteContext(ctx context.Context) DeleteOption {
return func(o *DeleteOptions) {
o.Context = ctx
}
}
// DeleteNamespace pass namespace to delete options
func DeleteNamespace(ns string) DeleteOption {
return func(o *DeleteOptions) {
o.Namespace = ns
}
}
// ListOptions configures an individual List operation
type ListOptions struct {
Context context.Context
Prefix string
Suffix string
Namespace string
Limit uint
Offset uint
}
// NewListOptions fills ListOptions struct with opts slice
func NewListOptions(opts ...ListOption) ListOptions {
options := ListOptions{}
@@ -250,59 +258,50 @@ func NewListOptions(opts ...ListOption) ListOptions {
return options
}
// ListOptions configures an individual List operation
type ListOptions struct {
Context context.Context
Database string
Prefix string
Suffix string
Namespace string
Table string
Limit uint
Offset uint
}
// ListOption sets values in ListOptions
type ListOption func(l *ListOptions)
// ListFrom the database and table
func ListFrom(database, table string) ListOption {
return func(l *ListOptions) {
l.Database = database
l.Table = table
// ListContext pass context.Context to list options
func ListContext(ctx context.Context) ListOption {
return func(o *ListOptions) {
o.Context = ctx
}
}
// ListPrefix returns all keys that are prefixed with key
func ListPrefix(p string) ListOption {
return func(l *ListOptions) {
l.Prefix = p
func ListPrefix(s string) ListOption {
return func(o *ListOptions) {
o.Prefix = s
}
}
// ListSuffix returns all keys that end with key
func ListSuffix(s string) ListOption {
return func(l *ListOptions) {
l.Suffix = s
return func(o *ListOptions) {
o.Suffix = s
}
}
// ListLimit limits the number of returned keys to l
func ListLimit(l uint) ListOption {
return func(lo *ListOptions) {
lo.Limit = l
// ListLimit limits the number of returned keys
func ListLimit(n uint) ListOption {
return func(o *ListOptions) {
o.Limit = n
}
}
// ListOffset starts returning responses from o. Use in conjunction with Limit for pagination.
func ListOffset(o uint) ListOption {
return func(l *ListOptions) {
l.Offset = o
// ListOffset use with Limit for pagination
func ListOffset(n uint) ListOption {
return func(o *ListOptions) {
o.Offset = n
}
}
// ExistsOption specifies Exists call options
type ExistsOption func(*ExistsOptions)
// ListNamespace pass namespace to list options
func ListNamespace(ns string) ListOption {
return func(o *ListOptions) {
o.Namespace = ns
}
}
// ExistsOptions holds options for Exists method
type ExistsOptions struct {
@@ -312,6 +311,9 @@ type ExistsOptions struct {
Namespace string
}
// ExistsOption specifies Exists call options
type ExistsOption func(*ExistsOptions)
// NewExistsOptions helper for Exists method
func NewExistsOptions(opts ...ExistsOption) ExistsOptions {
options := ExistsOptions{
@@ -322,3 +324,24 @@ func NewExistsOptions(opts ...ExistsOption) ExistsOptions {
}
return options
}
// ExistsContext pass context.Context to exist options
func ExistsContext(ctx context.Context) ExistsOption {
return func(o *ExistsOptions) {
o.Context = ctx
}
}
// ExistsNamespace pass namespace to exist options
func ExistsNamespace(ns string) ExistsOption {
return func(o *ExistsOptions) {
o.Namespace = ns
}
}
// WrapStore adds a store Wrapper to a list of options passed into the store
//func WrapStore(w Wrapper) Option {
// return func(o *Options) {
// o.Wrappers = append(o.Wrappers, w)
// }
//}

View File

@@ -5,8 +5,6 @@ package store
import (
"context"
"errors"
"github.com/unistack-org/micro/v3/metadata"
)
var (
@@ -42,12 +40,3 @@ type Store interface {
// String returns the name of the implementation.
String() string
}
// Value is an item stored or retrieved from a Store
// may be used in store implementations to provide metadata
type Value struct {
// Data holds underline struct
Data interface{} `json:"data"`
// Metadata associated with data for indexing
Metadata metadata.Metadata `json:"metadata"`
}

84
store/wrapper.go Normal file
View File

@@ -0,0 +1,84 @@
package store
import (
"context"
)
// LogfFunc function used for Logf method
//type LogfFunc func(ctx context.Context, level Level, msg string, args ...interface{})
//type Wrapper interface {
// Logf logs message with needed level
//Logf(LogfFunc) LogfFunc
//}
type NamespaceStore struct {
s Store
ns string
}
var (
_ Store = &NamespaceStore{}
)
func NewNamespaceStore(s Store, ns string) Store {
return &NamespaceStore{s: s, ns: ns}
}
func (w *NamespaceStore) Init(opts ...Option) error {
return w.s.Init(opts...)
}
func (w *NamespaceStore) Connect(ctx context.Context) error {
return w.s.Connect(ctx)
}
func (w *NamespaceStore) Disconnect(ctx context.Context) error {
return w.s.Disconnect(ctx)
}
func (w *NamespaceStore) Read(ctx context.Context, key string, val interface{}, opts ...ReadOption) error {
return w.s.Read(ctx, key, val, append(opts, ReadNamespace(w.ns))...)
}
func (w *NamespaceStore) Write(ctx context.Context, key string, val interface{}, opts ...WriteOption) error {
return w.s.Write(ctx, key, val, append(opts, WriteNamespace(w.ns))...)
}
func (w *NamespaceStore) Delete(ctx context.Context, key string, opts ...DeleteOption) error {
return w.s.Delete(ctx, key, append(opts, DeleteNamespace(w.ns))...)
}
func (w *NamespaceStore) Exists(ctx context.Context, key string, opts ...ExistsOption) error {
return w.s.Exists(ctx, key, append(opts, ExistsNamespace(w.ns))...)
}
func (w *NamespaceStore) List(ctx context.Context, opts ...ListOption) ([]string, error) {
return w.s.List(ctx, append(opts, ListNamespace(w.ns))...)
}
func (w *NamespaceStore) Options() Options {
return w.s.Options()
}
func (w *NamespaceStore) Name() string {
return w.s.Name()
}
func (w *NamespaceStore) String() string {
return w.s.String()
}
//type NamespaceWrapper struct{}
//func NewNamespaceWrapper() Wrapper {
// return &NamespaceWrapper{}
//}
/*
func (w *OmitWrapper) Logf(fn LogfFunc) LogfFunc {
return func(ctx context.Context, level Level, msg string, args ...interface{}) {
fn(ctx, level, msg, getArgs(args)...)
}
}
*/

View File

@@ -6,8 +6,8 @@ import (
)
type memorySync struct {
options Options
locks map[string]*memoryLock
options Options
mtx gosync.RWMutex
}

View File

@@ -5,10 +5,8 @@ import (
"errors"
)
var (
// ErrLockTimeout error
ErrLockTimeout = errors.New("lock timeout")
)
// ErrLockTimeout error
var ErrLockTimeout = errors.New("lock timeout")
// Sync is an interface for distributed synchronization
type Sync interface {

View File

@@ -38,7 +38,6 @@ type noopSpan struct {
}
func (s *noopSpan) Finish(opts ...SpanOption) {
}
func (s *noopSpan) Context() context.Context {
@@ -50,7 +49,6 @@ func (s *noopSpan) Tracer() Tracer {
}
func (s *noopSpan) AddEvent(name string, opts ...EventOption) {
}
func (s *noopSpan) SetName(name string) {
@@ -58,7 +56,6 @@ func (s *noopSpan) SetName(name string) {
}
func (s *noopSpan) SetLabels(labels ...Label) {
}
// NewTracer returns new memory tracer

View File

@@ -2,13 +2,11 @@ package tracer
import "github.com/unistack-org/micro/v3/logger"
type SpanOptions struct {
}
type SpanOptions struct{}
type SpanOption func(o *SpanOptions)
type EventOptions struct {
}
type EventOptions struct{}
type EventOption func(o *EventOptions)

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