Compare commits

..

71 Commits

Author SHA1 Message Date
6dc7e792c8 logger improvements
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-11-10 22:18:16 +03:00
81649d51e1 Merge branch 'master' of https://github.com/unistack-org/micro 2020-11-10 11:01:14 +03:00
23f5d10ccb server: remove unused code
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-11-10 11:00:37 +03:00
e3f235acc1 api/handler/rpc: fix corner cases
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-11-10 11:00:06 +03:00
fa9ef1c816 simplify service stop waiting
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-11-10 10:59:26 +03:00
77dab8ee15 server: remove unneded chan in noop server
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-11-10 10:58:32 +03:00
51fbff3e4a metadata: add checks for nil context
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-11-10 10:57:33 +03:00
bd6493327f Merge pull request #7 from itzmanish/protoc-gen-micro-fix
generate SendAndClose() and CloseAndRecv() on streams.
2020-11-07 15:53:00 +03:00
Manish
2141e9631c generate SendAndClose() and CloseAndRecv() on streams. 2020-11-07 14:55:33 +05:30
be8d09c663 network/transport: fix default dial timeout
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-11-06 11:18:12 +03:00
72bbbe3817 client: remove cache responses
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-11-06 00:25:09 +03:00
c92add984c allow to return all stuff from service opts
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-11-06 00:04:00 +03:00
3542d6c824 store: fix comment
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-11-06 00:03:40 +03:00
dc63d96e0b move store test to micro-tests repo
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-11-05 23:56:10 +03:00
7c9a7e84c7 not use selfsigned certs
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-11-05 23:55:53 +03:00
31180758b4 move avay grpc stuff to micro-server-grpc
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-11-05 23:46:11 +03:00
ce25a41fe1 remove cache as store can do the same thing
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-11-05 23:35:55 +03:00
8fa8afdfa4 fix namespace server issue
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-11-05 22:35:05 +03:00
e127547799 add registry helpers
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-11-05 21:18:05 +03:00
1fbf8b2e20 Merge pull request #6 from unistack-org/logger
rewrite logger
2020-11-05 00:49:03 +03:00
e41bb5ebc5 rewrite logger
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-11-05 00:23:05 +03:00
7c311aea19 api: extract routers to external repos
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-11-05 00:22:53 +03:00
8a2b122015 many fixes for lint and context.Context usage (#5)
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-11-03 02:02:32 +03:00
40b0870cf8 fix linting (#4)
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-11-03 01:08:23 +03:00
e6ab6d50eb remove unneeded Error field from broker.Message
close github.com/unistack-org/micro-roadmap/issues/26 (#3)

Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-11-02 23:45:41 +03:00
a9eff06976 fix repocard issues (#2)
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-11-02 13:25:29 +03:00
416fe5e4c8 run tests from micro-tests repo
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-10-30 23:52:05 +03:00
ddb53bf8e4 add slack badge
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-10-29 16:16:31 +03:00
0e6efda528 minor cleanup
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-10-28 17:48:39 +03:00
f2413a7789 disconnect from stuff on service stop
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-10-28 12:02:57 +03:00
9553f46cf4 connect to all stuff on start
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-10-28 10:51:09 +03:00
14c97d59c1 many improvements with options and noop stuff
* add many options helpers
* fix noop client to allow publish messages to topic in broker
* fix noop server to allow registering in registry
* fix noop server to allow subscribe to topic in broker
* fix new service initialization

Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-10-16 09:38:57 +03:00
a59aae760f metadata: use new helper
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-10-12 17:20:52 +03:00
0a5b34a07b metadata: small optimization and export default metadata size
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-10-12 17:17:59 +03:00
62502ad720 always generate call with CamelCased name
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-10-12 16:19:30 +03:00
6e43ae7190 add client publish option func
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-10-12 12:09:09 +03:00
0e1f744fcc add helper for publish options
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-10-10 00:47:09 +03:00
2fc47782cf use metadata helper
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-10-09 16:51:58 +03:00
34d93306d6 new registry util func
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-10-09 16:49:29 +03:00
336868ed0d move helper to proper place
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-10-09 16:21:47 +03:00
2682f15b8e move helper to proper place
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-10-09 16:20:10 +03:00
4c12e38c01 move generic code from grpc server implementation
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-10-09 16:15:36 +03:00
62bfe9c06e allow to publish message via broker on noop client
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-10-09 13:43:04 +03:00
24be220f91 close micro-roadmap/issues/12
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-10-09 12:15:55 +03:00
cacd33e84f metadata: fix tests
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-10-01 16:05:05 +03:00
9475003059 fix concurrent map usage
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-10-01 16:00:01 +03:00
8532ccebba metadata: avoid allocations on delete
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-09-30 16:21:47 +03:00
9c55b1d06a fix metadata issues with uppercase letters
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-09-30 16:14:54 +03:00
efd9075d9b add error vars
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-09-28 13:08:53 +03:00
4c4fa00a5d add useful func
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-09-22 13:33:57 +03:00
21d5ca1cdd rename func
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-09-22 13:17:38 +03:00
ec3c1a02fc expose useful broker and server methods
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-09-22 13:15:05 +03:00
dc5dc6ab5b update
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-09-20 17:02:41 +03:00
1cbd1d2bad fix
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-09-20 16:57:54 +03:00
aa667728a1 move transport
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-09-20 16:52:26 +03:00
9b11ea527a add Wait option
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-09-20 15:48:07 +03:00
5787a1afb8 more useful router new options func
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-09-17 15:44:35 +03:00
74c10f1139 more idiomatic names inside generated protoc files
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-09-17 15:44:05 +03:00
7e3fac8937 fix store context issues
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-09-17 15:41:49 +03:00
6021edc855 add more context to store
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-09-17 15:18:01 +03:00
8817c110d0 add context to store
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-09-17 15:15:42 +03:00
d59db9df16 check for nil cmd
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-09-14 12:58:51 +03:00
2d1e6db9fd add context in server handler option
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-09-11 11:55:31 +03:00
5bfca99627 fix old proto
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-09-10 15:17:23 +03:00
9ea3149b60 fixup opts
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-09-10 00:25:33 +03:00
8f03480ed2 update go.mod
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-09-10 00:08:59 +03:00
caec730248 move out tracers
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-09-10 00:06:29 +03:00
f1fde75567 fix init
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-09-07 15:29:43 +03:00
5fe3a46732 init default stuff
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-09-07 13:38:52 +03:00
e7d418183b fixup logger usage (#33)
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-09-05 02:43:16 +03:00
c576749b57 noop impl (#32)
* improve logger usage
* add noop client and server

Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2020-09-05 02:11:29 +03:00
141 changed files with 3174 additions and 4542 deletions

View File

@@ -1,6 +1,6 @@
name: build name: build
on: on:
push: push:
branches: branches:
- master - master
jobs: jobs:
@@ -12,20 +12,36 @@ jobs:
uses: actions/setup-go@v1 uses: actions/setup-go@v1
with: with:
go-version: 1.15 go-version: 1.15
- name: checkout
uses: actions/checkout@v2
- name: cache - name: cache
uses: actions/cache@v2 uses: actions/cache@v2
with: with:
path: ~/go/pkg/mod path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: ${{ runner.os }}-go- restore-keys: ${{ runner.os }}-go-
- name: deps - name: sdk checkout
uses: actions/checkout@v2
- name: sdk deps
run: go get -v -t -d ./... run: go get -v -t -d ./...
- name: test - name: sdk test
env: env:
INTEGRATION_TESTS: yes INTEGRATION_TESTS: yes
run: go test -mod readonly -v ./... run: go test -mod readonly -v ./...
- name: tests checkout
uses: actions/checkout@v2
with:
repository: unistack-org/micro-tests
ref: refs/heads/master
path: micro-tests
fetch-depth: 1
- name: tests deps
run: |
cd micro-tests
go mod edit -replace="github.com/unistack-org/micro/v3=../"
go get -v -t -d ./...
- name: tests test
env:
INTEGRATION_TESTS: yes
run: cd micro-tests && go test -mod readonly -v ./...
lint: lint:
name: lint name: lint
runs-on: ubuntu-latest runs-on: ubuntu-latest

View File

@@ -12,20 +12,36 @@ jobs:
uses: actions/setup-go@v1 uses: actions/setup-go@v1
with: with:
go-version: 1.15 go-version: 1.15
- name: checkout
uses: actions/checkout@v2
- name: cache - name: cache
uses: actions/cache@v2 uses: actions/cache@v2
with: with:
path: ~/go/pkg/mod path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: ${{ runner.os }}-go- restore-keys: ${{ runner.os }}-go-
- name: deps - name: sdk checkout
uses: actions/checkout@v2
- name: sdk deps
run: go get -v -t -d ./... run: go get -v -t -d ./...
- name: test - name: sdk test
env: env:
INTEGRATION_TESTS: yes INTEGRATION_TESTS: yes
run: go test -mod readonly -v ./... run: go test -mod readonly -v ./...
- name: tests checkout
uses: actions/checkout@v2
with:
repository: unistack-org/micro-tests
ref: refs/heads/master
path: micro-tests
fetch-depth: 1
- name: tests deps
run: |
cd micro-tests
go mod edit -replace="github.com/unistack-org/micro/v3=../"
go get -v -t -d ./...
- name: tests test
env:
INTEGRATION_TESTS: yes
run: cd micro-tests && go test -mod readonly -v ./...
lint: lint:
name: lint name: lint
runs-on: ubuntu-latest runs-on: ubuntu-latest

View File

@@ -1,4 +1,4 @@
# Micro [![License](https://img.shields.io/:license-apache-blue.svg)](https://opensource.org/licenses/Apache-2.0) [![Doc](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/github.com/unistack-org/micro/v3?tab=overview) [![Status](https://github.com/unistack-org/micro/workflows/build/badge.svg?branch=master)](https://github.com/unistack-org/micro/actions?query=workflow%3Abuild+branch%3Amaster+event%3Apush) [![Lint](https://goreportcard.com/badge/github.com/unistack-org/micro)](https://goreportcard.com/report/github.com/unistack-org/micro) # Micro [![License](https://img.shields.io/:license-apache-blue.svg)](https://opensource.org/licenses/Apache-2.0) [![Doc](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/github.com/unistack-org/micro/v3?tab=overview) [![Status](https://github.com/unistack-org/micro/workflows/build/badge.svg?branch=master)](https://github.com/unistack-org/micro/actions?query=workflow%3Abuild+branch%3Amaster+event%3Apush) [![Lint](https://goreportcard.com/badge/github.com/unistack-org/micro)](https://goreportcard.com/report/github.com/unistack-org/micro) [![Slack](https://img.shields.io/static/v1?label=micro&message=slack&color=blueviolet)](https://unistack-org.slack.com/messages/default)
Micro is a standard library for microservices. Micro is a standard library for microservices.

View File

@@ -10,13 +10,13 @@ import (
jsonpatch "github.com/evanphx/json-patch/v5" jsonpatch "github.com/evanphx/json-patch/v5"
"github.com/oxtoacart/bpool" "github.com/oxtoacart/bpool"
jsonrpc "github.com/unistack-org/micro-codec-jsonrpc"
protorpc "github.com/unistack-org/micro-codec-protorpc"
"github.com/unistack-org/micro/v3/api" "github.com/unistack-org/micro/v3/api"
"github.com/unistack-org/micro/v3/api/handler" "github.com/unistack-org/micro/v3/api/handler"
"github.com/unistack-org/micro/v3/api/internal/proto" "github.com/unistack-org/micro/v3/api/internal/proto"
"github.com/unistack-org/micro/v3/client" "github.com/unistack-org/micro/v3/client"
"github.com/unistack-org/micro/v3/codec" "github.com/unistack-org/micro/v3/codec"
"github.com/unistack-org/micro/v3/codec/jsonrpc"
"github.com/unistack-org/micro/v3/codec/protorpc"
"github.com/unistack-org/micro/v3/errors" "github.com/unistack-org/micro/v3/errors"
"github.com/unistack-org/micro/v3/logger" "github.com/unistack-org/micro/v3/logger"
"github.com/unistack-org/micro/v3/metadata" "github.com/unistack-org/micro/v3/metadata"
@@ -224,7 +224,7 @@ func requestPayload(r *http.Request) ([]byte, error) {
case strings.Contains(ct, "application/json-rpc"): case strings.Contains(ct, "application/json-rpc"):
msg := codec.Message{ msg := codec.Message{
Type: codec.Request, Type: codec.Request,
Header: make(map[string]string), Header: metadata.New(0),
} }
c := jsonrpc.NewCodec(&buffer{r.Body}) c := jsonrpc.NewCodec(&buffer{r.Body})
if err = c.ReadHeader(&msg, codec.Request); err != nil { if err = c.ReadHeader(&msg, codec.Request); err != nil {
@@ -238,7 +238,7 @@ func requestPayload(r *http.Request) ([]byte, error) {
case strings.Contains(ct, "application/proto-rpc"), strings.Contains(ct, "application/octet-stream"): case strings.Contains(ct, "application/proto-rpc"), strings.Contains(ct, "application/octet-stream"):
msg := codec.Message{ msg := codec.Message{
Type: codec.Request, Type: codec.Request,
Header: make(map[string]string), Header: metadata.New(0),
} }
c := protorpc.NewCodec(&buffer{r.Body}) c := protorpc.NewCodec(&buffer{r.Body})
if err = c.ReadHeader(&msg, codec.Request); err != nil { if err = c.ReadHeader(&msg, codec.Request); err != nil {
@@ -250,10 +250,12 @@ func requestPayload(r *http.Request) ([]byte, error) {
} }
return raw.Marshal() return raw.Marshal()
case strings.Contains(ct, "application/www-x-form-urlencoded"): case strings.Contains(ct, "application/www-x-form-urlencoded"):
r.ParseForm() if err = r.ParseForm(); err != nil {
return nil, err
}
// generate a new set of values from the form // generate a new set of values from the form
vals := make(map[string]string) vals := make(map[string]string, len(r.Form))
for k, v := range r.Form { for k, v := range r.Form {
vals[k] = strings.Join(v, ",") vals[k] = strings.Join(v, ",")
} }
@@ -268,7 +270,7 @@ func requestPayload(r *http.Request) ([]byte, error) {
// dont user metadata.FromContext as it mangles names // dont user metadata.FromContext as it mangles names
md, ok := metadata.FromContext(ctx) md, ok := metadata.FromContext(ctx)
if !ok { if !ok {
md = make(map[string]string) md = metadata.New(0)
} }
// allocate maximum // allocate maximum
@@ -444,8 +446,8 @@ func writeError(w http.ResponseWriter, r *http.Request, err error) {
_, werr := w.Write([]byte(ce.Error())) _, werr := w.Write([]byte(ce.Error()))
if werr != nil { if werr != nil {
if logger.V(logger.ErrorLevel, logger.DefaultLogger) { if logger.V(logger.ErrorLevel) {
logger.Error(werr) logger.Error(werr.Error())
} }
} }
} }
@@ -470,8 +472,8 @@ func writeResponse(w http.ResponseWriter, r *http.Request, rsp []byte) {
// write response // write response
_, err := w.Write(rsp) _, err := w.Write(rsp)
if err != nil { if err != nil {
if logger.V(logger.ErrorLevel, logger.DefaultLogger) { if logger.V(logger.ErrorLevel) {
logger.Error(err) logger.Error(err.Error())
} }
} }

View File

@@ -2,12 +2,12 @@ package rpc
import ( import (
"bytes" "bytes"
"encoding/json"
"net/http" "net/http"
"testing" "testing"
"github.com/golang/protobuf/proto"
go_api "github.com/unistack-org/micro/v3/api/proto" go_api "github.com/unistack-org/micro/v3/api/proto"
jsonpb "google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
) )
func TestRequestPayloadFromRequest(t *testing.T) { func TestRequestPayloadFromRequest(t *testing.T) {
@@ -22,7 +22,7 @@ func TestRequestPayloadFromRequest(t *testing.T) {
t.Fatal("Failed to marshal proto", err) t.Fatal("Failed to marshal proto", err)
} }
jsonBytes, err := json.Marshal(protoEvent) jsonBytes, err := jsonpb.Marshal(&protoEvent)
if err != nil { if err != nil {
t.Fatal("Failed to marshal proto to JSON ", err) t.Fatal("Failed to marshal proto to JSON ", err)
} }

View File

@@ -44,13 +44,15 @@ func serveWebsocket(ctx context.Context, w http.ResponseWriter, r *http.Request,
case "binary": case "binary":
hdr["Sec-WebSocket-Protocol"] = []string{"binary"} hdr["Sec-WebSocket-Protocol"] = []string{"binary"}
op = ws.OpBinary op = ws.OpBinary
default:
op = ws.OpBinary
} }
} }
} }
payload, err := requestPayload(r) payload, err := requestPayload(r)
if err != nil { if err != nil {
if logger.V(logger.ErrorLevel, logger.DefaultLogger) { if logger.V(logger.ErrorLevel) {
logger.Error(err) logger.Error(err.Error())
} }
return return
} }
@@ -72,16 +74,16 @@ func serveWebsocket(ctx context.Context, w http.ResponseWriter, r *http.Request,
conn, rw, _, err := upgrader.Upgrade(r, w) conn, rw, _, err := upgrader.Upgrade(r, w)
if err != nil { if err != nil {
if logger.V(logger.ErrorLevel, logger.DefaultLogger) { if logger.V(logger.ErrorLevel) {
logger.Error(err) logger.Error(err.Error())
} }
return return
} }
defer func() { defer func() {
if err := conn.Close(); err != nil { if err := conn.Close(); err != nil {
if logger.V(logger.ErrorLevel, logger.DefaultLogger) { if logger.V(logger.ErrorLevel) {
logger.Error(err) logger.Error(err.Error())
} }
return return
} }
@@ -116,16 +118,16 @@ func serveWebsocket(ctx context.Context, w http.ResponseWriter, r *http.Request,
// create a new stream // create a new stream
stream, err := c.Stream(ctx, req, callOpt) stream, err := c.Stream(ctx, req, callOpt)
if err != nil { if err != nil {
if logger.V(logger.ErrorLevel, logger.DefaultLogger) { if logger.V(logger.ErrorLevel) {
logger.Error(err) logger.Error(err.Error())
} }
return return
} }
if request != nil { if request != nil {
if err = stream.Send(request); err != nil { if err = stream.Send(request); err != nil {
if logger.V(logger.ErrorLevel, logger.DefaultLogger) { if logger.V(logger.ErrorLevel) {
logger.Error(err) logger.Error(err.Error())
} }
return return
} }
@@ -150,22 +152,22 @@ func serveWebsocket(ctx context.Context, w http.ResponseWriter, r *http.Request,
if strings.Contains(err.Error(), "context canceled") { if strings.Contains(err.Error(), "context canceled") {
return return
} }
if logger.V(logger.ErrorLevel, logger.DefaultLogger) { if logger.V(logger.ErrorLevel) {
logger.Error(err) logger.Error(err.Error())
} }
return return
} }
// write the response // write the response
if err := wsutil.WriteServerMessage(rw, op, buf); err != nil { if err := wsutil.WriteServerMessage(rw, op, buf); err != nil {
if logger.V(logger.ErrorLevel, logger.DefaultLogger) { if logger.V(logger.ErrorLevel) {
logger.Error(err) logger.Error(err.Error())
} }
return return
} }
if err = rw.Flush(); err != nil { if err = rw.Flush(); err != nil {
if logger.V(logger.ErrorLevel, logger.DefaultLogger) { if logger.V(logger.ErrorLevel) {
logger.Error(err) logger.Error(err.Error())
} }
return return
} }
@@ -195,8 +197,8 @@ func writeLoop(rw io.ReadWriter, stream client.Stream) {
return return
} }
} }
if logger.V(logger.ErrorLevel, logger.DefaultLogger) { if logger.V(logger.ErrorLevel) {
logger.Error(err) logger.Error(err.Error())
} }
return return
} }
@@ -212,8 +214,8 @@ func writeLoop(rw io.ReadWriter, stream client.Stream) {
// if the extracted payload isn't empty lets use it // if the extracted payload isn't empty lets use it
request := &raw.Frame{Data: buf} request := &raw.Frame{Data: buf}
if err := stream.Send(request); err != nil { if err := stream.Send(request); err != nil {
if logger.V(logger.ErrorLevel, logger.DefaultLogger) { if logger.V(logger.ErrorLevel) {
logger.Error(err) logger.Error(err.Error())
} }
return return
} }

View File

@@ -9,10 +9,12 @@ import (
"github.com/unistack-org/micro/v3/api/resolver" "github.com/unistack-org/micro/v3/api/resolver"
) )
// Resolver struct
type Resolver struct { type Resolver struct {
opts resolver.Options opts resolver.Options
} }
// Resolve func to resolve enndpoint
func (r *Resolver) Resolve(req *http.Request, opts ...resolver.ResolveOption) (*resolver.Endpoint, error) { func (r *Resolver) Resolve(req *http.Request, opts ...resolver.ResolveOption) (*resolver.Endpoint, error) {
// parse options // parse options
options := resolver.NewResolveOptions(opts...) options := resolver.NewResolveOptions(opts...)
@@ -39,6 +41,7 @@ func (r *Resolver) String() string {
return "grpc" return "grpc"
} }
// NewResolver is used to create new Resolver
func NewResolver(opts ...resolver.Option) resolver.Resolver { func NewResolver(opts ...resolver.Option) resolver.Resolver {
return &Resolver{opts: resolver.NewOptions(opts...)} return &Resolver{opts: resolver.NewOptions(opts...)}
} }

View File

@@ -4,11 +4,13 @@ import (
"github.com/unistack-org/micro/v3/registry" "github.com/unistack-org/micro/v3/registry"
) )
// Options struct
type Options struct { type Options struct {
Handler string Handler string
ServicePrefix string ServicePrefix string
} }
// Option func
type Option func(o *Options) type Option func(o *Options)
// WithHandler sets the handler being used // WithHandler sets the handler being used
@@ -27,7 +29,7 @@ func WithServicePrefix(p string) Option {
// NewOptions returns new initialised options // NewOptions returns new initialised options
func NewOptions(opts ...Option) Options { func NewOptions(opts ...Option) Options {
var options Options options := Options{}
for _, o := range opts { for _, o := range opts {
o(&options) o(&options)
} }
@@ -51,13 +53,10 @@ func Domain(n string) ResolveOption {
// NewResolveOptions returns new initialised resolve options // NewResolveOptions returns new initialised resolve options
func NewResolveOptions(opts ...ResolveOption) ResolveOptions { func NewResolveOptions(opts ...ResolveOption) ResolveOptions {
var options ResolveOptions options := ResolveOptions{Domain: registry.DefaultDomain}
for _, o := range opts { for _, o := range opts {
o(&options) o(&options)
} }
if len(options.Domain) == 0 {
options.Domain = registry.DefaultDomain
}
return options return options
} }

View File

@@ -7,7 +7,9 @@ import (
) )
var ( var (
ErrNotFound = errors.New("not found") // ErrNotFound returned when endpoint is not found
ErrNotFound = errors.New("not found")
// ErrInvalidPath returned on invalid path
ErrInvalidPath = errors.New("invalid path") ErrInvalidPath = errors.New("invalid path")
) )
@@ -19,7 +21,7 @@ type Resolver interface {
// Endpoint is the endpoint for a http request // Endpoint is the endpoint for a http request
type Endpoint struct { type Endpoint struct {
// e.g greeter // Endpoint name e.g greeter
Name string Name string
// HTTP Host e.g example.com // HTTP Host e.g example.com
Host string Host string

View File

@@ -54,7 +54,9 @@ func (r *Resolver) Domain(req *http.Request) string {
// extract the top level domain plus one (e.g. 'myapp.com') // extract the top level domain plus one (e.g. 'myapp.com')
domain, err := publicsuffix.EffectiveTLDPlusOne(host) domain, err := publicsuffix.EffectiveTLDPlusOne(host)
if err != nil { if err != nil {
logger.Debugf("Unable to extract domain from %v", host) if logger.V(logger.DebugLevel) {
logger.Debug("Unable to extract domain from %v", host)
}
return "" return ""
} }

View File

@@ -1,6 +1,8 @@
package router package router
import ( import (
"context"
"github.com/unistack-org/micro/v3/api/resolver" "github.com/unistack-org/micro/v3/api/resolver"
"github.com/unistack-org/micro/v3/api/resolver/vpath" "github.com/unistack-org/micro/v3/api/resolver/vpath"
"github.com/unistack-org/micro/v3/registry" "github.com/unistack-org/micro/v3/registry"
@@ -10,12 +12,14 @@ type Options struct {
Handler string Handler string
Registry registry.Registry Registry registry.Registry
Resolver resolver.Resolver Resolver resolver.Resolver
Context context.Context
} }
type Option func(o *Options) type Option func(o *Options)
func NewOptions(opts ...Option) Options { func NewOptions(opts ...Option) Options {
options := Options{ options := Options{
Context: context.Background(),
Handler: "meta", Handler: "meta",
} }
@@ -32,18 +36,28 @@ func NewOptions(opts ...Option) Options {
return options return options
} }
// WithContext sets the context
func WithContext(ctx context.Context) Option {
return func(o *Options) {
o.Context = ctx
}
}
// WithHandler sets the handler
func WithHandler(h string) Option { func WithHandler(h string) Option {
return func(o *Options) { return func(o *Options) {
o.Handler = h o.Handler = h
} }
} }
// WithRegistry sets the registry
func WithRegistry(r registry.Registry) Option { func WithRegistry(r registry.Registry) Option {
return func(o *Options) { return func(o *Options) {
o.Registry = r o.Registry = r
} }
} }
// WithResolver sets the resolver
func WithResolver(r resolver.Resolver) Option { func WithResolver(r resolver.Resolver) Option {
return func(o *Options) { return func(o *Options) {
o.Resolver = r o.Resolver = r

View File

@@ -1,498 +0,0 @@
// Package registry provides a dynamic api service router
package registry
import (
"errors"
"fmt"
"net/http"
"regexp"
"strings"
"sync"
"time"
"github.com/unistack-org/micro/v3/api"
"github.com/unistack-org/micro/v3/api/router"
"github.com/unistack-org/micro/v3/logger"
"github.com/unistack-org/micro/v3/metadata"
"github.com/unistack-org/micro/v3/registry"
util "github.com/unistack-org/micro/v3/util/router"
)
// endpoint struct, that holds compiled pcre
type endpoint struct {
hostregs []*regexp.Regexp
pathregs []util.Pattern
pcreregs []*regexp.Regexp
}
// router is the default router
type registryRouter struct {
exit chan bool
opts router.Options
sync.RWMutex
eps map[string]*api.Service
// compiled regexp for host and path
ceps map[string]*endpoint
}
func (r *registryRouter) isClosed() bool {
select {
case <-r.exit:
return true
default:
return false
}
}
// refresh list of api services
func (r *registryRouter) refresh() {
var attempts int
for {
services, err := r.opts.Registry.ListServices()
if err != nil {
attempts++
if logger.V(logger.ErrorLevel, logger.DefaultLogger) {
logger.Errorf("unable to list services: %v", err)
}
time.Sleep(time.Duration(attempts) * time.Second)
continue
}
attempts = 0
// for each service, get service and store endpoints
for _, s := range services {
service, err := r.opts.Registry.GetService(s.Name)
if err != nil {
if logger.V(logger.ErrorLevel, logger.DefaultLogger) {
logger.Errorf("unable to get service: %v", err)
}
continue
}
r.store(service)
}
// refresh list in 10 minutes... cruft
// use registry watching
select {
case <-time.After(time.Minute * 10):
case <-r.exit:
return
}
}
}
// process watch event
func (r *registryRouter) process(res *registry.Result) {
// skip these things
if res == nil || res.Service == nil {
return
}
// get entry from cache
service, err := r.opts.Registry.GetService(res.Service.Name)
if err != nil {
if logger.V(logger.ErrorLevel, logger.DefaultLogger) {
logger.Errorf("unable to get %v service: %v", res.Service.Name, err)
}
return
}
// update our local endpoints
r.store(service)
}
// store local endpoint cache
func (r *registryRouter) store(services []*registry.Service) {
// endpoints
eps := map[string]*api.Service{}
// services
names := map[string]bool{}
// create a new endpoint mapping
for _, service := range services {
// set names we need later
names[service.Name] = true
// map per endpoint
for _, sep := range service.Endpoints {
// create a key service:endpoint_name
key := fmt.Sprintf("%s.%s", service.Name, sep.Name)
// decode endpoint
end := api.Decode(sep.Metadata)
// no endpoint or no name
if end == nil || len(end.Name) == 0 {
continue
}
// if we got nothing skip
if err := api.Validate(end); err != nil {
if logger.V(logger.TraceLevel, logger.DefaultLogger) {
logger.Tracef("endpoint validation failed: %v", err)
}
continue
}
// try get endpoint
ep, ok := eps[key]
if !ok {
ep = &api.Service{Name: service.Name}
}
// overwrite the endpoint
ep.Endpoint = end
// append services
ep.Services = append(ep.Services, service)
// store it
eps[key] = ep
}
}
r.Lock()
defer r.Unlock()
// delete any existing eps for services we know
for key, service := range r.eps {
// skip what we don't care about
if !names[service.Name] {
continue
}
// ok we know this thing
// delete delete delete
delete(r.eps, key)
}
// now set the eps we have
for name, ep := range eps {
r.eps[name] = ep
cep := &endpoint{}
for _, h := range ep.Endpoint.Host {
if h == "" || h == "*" {
continue
}
hostreg, err := regexp.CompilePOSIX(h)
if err != nil {
if logger.V(logger.TraceLevel, logger.DefaultLogger) {
logger.Tracef("endpoint have invalid host regexp: %v", err)
}
continue
}
cep.hostregs = append(cep.hostregs, hostreg)
}
for _, p := range ep.Endpoint.Path {
var pcreok bool
if p[0] == '^' && p[len(p)-1] == '$' {
pcrereg, err := regexp.CompilePOSIX(p)
if err == nil {
cep.pcreregs = append(cep.pcreregs, pcrereg)
pcreok = true
}
}
rule, err := util.Parse(p)
if err != nil && !pcreok {
if logger.V(logger.TraceLevel, logger.DefaultLogger) {
logger.Tracef("endpoint have invalid path pattern: %v", err)
}
continue
} else if err != nil && pcreok {
continue
}
tpl := rule.Compile()
pathreg, err := util.NewPattern(tpl.Version, tpl.OpCodes, tpl.Pool, "")
if err != nil {
if logger.V(logger.TraceLevel, logger.DefaultLogger) {
logger.Tracef("endpoint have invalid path pattern: %v", err)
}
continue
}
cep.pathregs = append(cep.pathregs, pathreg)
}
r.ceps[name] = cep
}
}
// watch for endpoint changes
func (r *registryRouter) watch() {
var attempts int
for {
if r.isClosed() {
return
}
// watch for changes
w, err := r.opts.Registry.Watch()
if err != nil {
attempts++
if logger.V(logger.ErrorLevel, logger.DefaultLogger) {
logger.Errorf("error watching endpoints: %v", err)
}
time.Sleep(time.Duration(attempts) * time.Second)
continue
}
ch := make(chan bool)
go func() {
select {
case <-ch:
w.Stop()
case <-r.exit:
w.Stop()
}
}()
// reset if we get here
attempts = 0
for {
// process next event
res, err := w.Next()
if err != nil {
if logger.V(logger.ErrorLevel, logger.DefaultLogger) {
logger.Errorf("error getting next endpoint: %v", err)
}
close(ch)
break
}
r.process(res)
}
}
}
func (r *registryRouter) Options() router.Options {
return r.opts
}
func (r *registryRouter) Close() error {
select {
case <-r.exit:
return nil
default:
close(r.exit)
}
return nil
}
func (r *registryRouter) Register(ep *api.Endpoint) error {
return nil
}
func (r *registryRouter) Deregister(ep *api.Endpoint) error {
return nil
}
func (r *registryRouter) Endpoint(req *http.Request) (*api.Service, error) {
if r.isClosed() {
return nil, errors.New("router closed")
}
r.RLock()
defer r.RUnlock()
var idx int
if len(req.URL.Path) > 0 && req.URL.Path != "/" {
idx = 1
}
path := strings.Split(req.URL.Path[idx:], "/")
// use the first match
// TODO: weighted matching
for n, e := range r.eps {
cep, ok := r.ceps[n]
if !ok {
continue
}
ep := e.Endpoint
var mMatch, hMatch, pMatch bool
// 1. try method
for _, m := range ep.Method {
if m == req.Method {
mMatch = true
break
}
}
if !mMatch {
continue
}
if logger.V(logger.DebugLevel, logger.DefaultLogger) {
logger.Debugf("api method match %s", req.Method)
}
// 2. try host
if len(ep.Host) == 0 {
hMatch = true
} else {
for idx, h := range ep.Host {
if h == "" || h == "*" {
hMatch = true
break
} else {
if cep.hostregs[idx].MatchString(req.URL.Host) {
hMatch = true
break
}
}
}
}
if !hMatch {
continue
}
if logger.V(logger.DebugLevel, logger.DefaultLogger) {
logger.Debugf("api host match %s", req.URL.Host)
}
// 3. try path via google.api path matching
for _, pathreg := range cep.pathregs {
matches, err := pathreg.Match(path, "")
if err != nil {
if logger.V(logger.DebugLevel, logger.DefaultLogger) {
logger.Debugf("api gpath not match %s != %v", path, pathreg)
}
continue
}
if logger.V(logger.DebugLevel, logger.DefaultLogger) {
logger.Debugf("api gpath match %s = %v", path, pathreg)
}
pMatch = true
ctx := req.Context()
md, ok := metadata.FromContext(ctx)
if !ok {
md = make(metadata.Metadata)
}
for k, v := range matches {
md[fmt.Sprintf("x-api-field-%s", k)] = v
}
md["x-api-body"] = ep.Body
*req = *req.Clone(metadata.NewContext(ctx, md))
break
}
if !pMatch {
// 4. try path via pcre path matching
for _, pathreg := range cep.pcreregs {
if !pathreg.MatchString(req.URL.Path) {
if logger.V(logger.DebugLevel, logger.DefaultLogger) {
logger.Debugf("api pcre path not match %s != %v", path, pathreg)
}
continue
}
if logger.V(logger.DebugLevel, logger.DefaultLogger) {
logger.Debugf("api pcre path match %s != %v", path, pathreg)
}
pMatch = true
break
}
}
if !pMatch {
continue
}
// TODO: Percentage traffic
// we got here, so its a match
return e, nil
}
// no match
return nil, errors.New("not found")
}
func (r *registryRouter) Route(req *http.Request) (*api.Service, error) {
if r.isClosed() {
return nil, errors.New("router closed")
}
// try get an endpoint
ep, err := r.Endpoint(req)
if err == nil {
return ep, nil
}
// error not nil
// ignore that shit
// TODO: don't ignore that shit
// get the service name
rp, err := r.opts.Resolver.Resolve(req)
if err != nil {
return nil, err
}
// service name
name := rp.Name
// get service
services, err := r.opts.Registry.GetService(name, registry.GetDomain(rp.Domain))
if err != nil {
return nil, err
}
// only use endpoint matching when the meta handler is set aka api.Default
switch r.opts.Handler {
// rpc handlers
case "meta", "api", "rpc":
handler := r.opts.Handler
// set default handler to api
if r.opts.Handler == "meta" {
handler = "rpc"
}
// construct api service
return &api.Service{
Name: name,
Endpoint: &api.Endpoint{
Name: rp.Method,
Handler: handler,
},
Services: services,
}, nil
// http handler
case "http", "proxy", "web":
// construct api service
return &api.Service{
Name: name,
Endpoint: &api.Endpoint{
Name: req.URL.String(),
Handler: r.opts.Handler,
Host: []string{req.Host},
Method: []string{req.Method},
Path: []string{req.URL.Path},
},
Services: services,
}, nil
}
return nil, errors.New("unknown handler")
}
func newRouter(opts ...router.Option) (*registryRouter, error) {
options := router.NewOptions(opts...)
if options.Registry == nil {
return nil, fmt.Errorf("registry is not set")
}
r := &registryRouter{
exit: make(chan bool),
opts: options,
eps: make(map[string]*api.Service),
ceps: make(map[string]*endpoint),
}
go r.watch()
go r.refresh()
return r, nil
}
// NewRouter returns the default router
func NewRouter(opts ...router.Option) (router.Router, error) {
return newRouter(opts...)
}

View File

@@ -1,38 +0,0 @@
package registry
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/unistack-org/micro/v3/registry"
)
func TestStoreRegex(t *testing.T) {
t.Skip()
router, err := newRouter()
if err != nil {
t.Fatal(err)
}
router.store([]*registry.Service{
{
Name: "Foobar",
Version: "latest",
Endpoints: []*registry.Endpoint{
{
Name: "foo",
Metadata: map[string]string{
"endpoint": "FooEndpoint",
"description": "Some description",
"method": "POST",
"path": "^/foo/$",
"handler": "rpc",
},
},
},
Metadata: map[string]string{},
},
},
)
assert.Len(t, router.ceps["Foobar.foo"].pcreregs, 1)
}

View File

@@ -1,257 +0,0 @@
// +build ignore
package router_test
import (
"context"
"fmt"
"io/ioutil"
"log"
"net/http"
"testing"
"time"
"github.com/unistack-org/micro/v3/api"
"github.com/unistack-org/micro/v3/api/handler"
"github.com/unistack-org/micro/v3/api/handler/rpc"
"github.com/unistack-org/micro/v3/api/router"
rregistry "github.com/unistack-org/micro/v3/api/router/registry"
rstatic "github.com/unistack-org/micro/v3/api/router/static"
"github.com/unistack-org/micro/v3/broker"
bmemory "github.com/unistack-org/micro/v3/broker/memory"
"github.com/unistack-org/micro/v3/client"
gcli "github.com/unistack-org/micro/v3/client/grpc"
rmemory "github.com/unistack-org/micro/v3/registry/memory"
rt "github.com/unistack-org/micro/v3/router"
regRouter "github.com/unistack-org/micro/v3/router/registry"
"github.com/unistack-org/micro/v3/server"
gsrv "github.com/unistack-org/micro/v3/server/grpc"
pb "github.com/unistack-org/micro/v3/server/grpc/proto"
)
// server is used to implement helloworld.GreeterServer.
type testServer struct {
}
// TestHello implements helloworld.GreeterServer
func (s *testServer) Call(ctx context.Context, req *pb.Request, rsp *pb.Response) error {
rsp.Msg = "Hello " + req.Uuid
return nil
}
// TestHello implements helloworld.GreeterServer
func (s *testServer) CallPcre(ctx context.Context, req *pb.Request, rsp *pb.Response) error {
rsp.Msg = "Hello " + req.Uuid
return nil
}
// TestHello implements helloworld.GreeterServer
func (s *testServer) CallPcreInvalid(ctx context.Context, req *pb.Request, rsp *pb.Response) error {
rsp.Msg = "Hello " + req.Uuid
return nil
}
func initial(t *testing.T) (server.Server, client.Client) {
r := rmemory.NewRegistry()
b := bmemory.NewBroker(broker.Registry(r))
// create a new client
s := gsrv.NewServer(
server.Name("foo"),
server.Broker(b),
server.Registry(r),
)
rtr := regRouter.NewRouter(
rt.Registry(r),
)
// create a new server
c := gcli.NewClient(
client.Router(rtr),
client.Broker(b),
)
h := &testServer{}
pb.RegisterTestHandler(s, h)
if err := s.Start(); err != nil {
t.Fatalf("failed to start: %v", err)
}
return s, c
}
func check(t *testing.T, addr string, path string, expected string) {
req, err := http.NewRequest("POST", fmt.Sprintf(path, addr), nil)
if err != nil {
t.Fatalf("Failed to created http.Request: %v", err)
}
req.Header.Set("Content-Type", "application/json")
rsp, err := (&http.Client{}).Do(req)
if err != nil {
t.Fatalf("Failed to created http.Request: %v", err)
}
defer rsp.Body.Close()
buf, err := ioutil.ReadAll(rsp.Body)
if err != nil {
t.Fatal(err)
}
jsonMsg := expected
if string(buf) != jsonMsg {
t.Fatalf("invalid message received, parsing error %s != %s", buf, jsonMsg)
}
}
func TestRouterRegistryPcre(t *testing.T) {
s, c := initial(t)
defer s.Stop()
router := rregistry.NewRouter(
router.WithHandler(rpc.Handler),
router.WithRegistry(s.Options().Registry),
)
hrpc := rpc.NewHandler(
handler.WithClient(c),
handler.WithRouter(router),
)
hsrv := &http.Server{
Handler: hrpc,
Addr: "127.0.0.1:6543",
WriteTimeout: 15 * time.Second,
ReadTimeout: 15 * time.Second,
IdleTimeout: 20 * time.Second,
MaxHeaderBytes: 1024 * 1024 * 1, // 1Mb
}
go func() {
log.Println(hsrv.ListenAndServe())
}()
defer hsrv.Close()
time.Sleep(1 * time.Second)
check(t, hsrv.Addr, "http://%s/api/v0/test/call/TEST", `{"msg":"Hello TEST"}`)
}
func TestRouterStaticPcre(t *testing.T) {
s, c := initial(t)
defer s.Stop()
router := rstatic.NewRouter(
router.WithHandler(rpc.Handler),
router.WithRegistry(s.Options().Registry),
)
err := router.Register(&api.Endpoint{
Name: "foo.Test.Call",
Method: []string{"POST"},
Path: []string{"^/api/v0/test/call/?$"},
Handler: "rpc",
})
if err != nil {
t.Fatal(err)
}
hrpc := rpc.NewHandler(
handler.WithClient(c),
handler.WithRouter(router),
)
hsrv := &http.Server{
Handler: hrpc,
Addr: "127.0.0.1:6543",
WriteTimeout: 15 * time.Second,
ReadTimeout: 15 * time.Second,
IdleTimeout: 20 * time.Second,
MaxHeaderBytes: 1024 * 1024 * 1, // 1Mb
}
go func() {
log.Println(hsrv.ListenAndServe())
}()
defer hsrv.Close()
time.Sleep(1 * time.Second)
check(t, hsrv.Addr, "http://%s/api/v0/test/call", `{"msg":"Hello "}`)
}
func TestRouterStaticGpath(t *testing.T) {
s, c := initial(t)
defer s.Stop()
router := rstatic.NewRouter(
router.WithHandler(rpc.Handler),
router.WithRegistry(s.Options().Registry),
)
err := router.Register(&api.Endpoint{
Name: "foo.Test.Call",
Method: []string{"POST"},
Path: []string{"/api/v0/test/call/{uuid}"},
Handler: "rpc",
})
if err != nil {
t.Fatal(err)
}
hrpc := rpc.NewHandler(
handler.WithClient(c),
handler.WithRouter(router),
)
hsrv := &http.Server{
Handler: hrpc,
Addr: "127.0.0.1:6543",
WriteTimeout: 15 * time.Second,
ReadTimeout: 15 * time.Second,
IdleTimeout: 20 * time.Second,
MaxHeaderBytes: 1024 * 1024 * 1, // 1Mb
}
go func() {
log.Println(hsrv.ListenAndServe())
}()
defer hsrv.Close()
time.Sleep(1 * time.Second)
check(t, hsrv.Addr, "http://%s/api/v0/test/call/TEST", `{"msg":"Hello TEST"}`)
}
func TestRouterStaticPcreInvalid(t *testing.T) {
var ep *api.Endpoint
var err error
s, c := initial(t)
defer s.Stop()
router := rstatic.NewRouter(
router.WithHandler(rpc.Handler),
router.WithRegistry(s.Options().Registry),
)
ep = &api.Endpoint{
Name: "foo.Test.Call",
Method: []string{"POST"},
Path: []string{"^/api/v0/test/call/?"},
Handler: "rpc",
}
err = router.Register(ep)
if err == nil {
t.Fatalf("invalid endpoint %v", ep)
}
ep = &api.Endpoint{
Name: "foo.Test.Call",
Method: []string{"POST"},
Path: []string{"/api/v0/test/call/?$"},
Handler: "rpc",
}
err = router.Register(ep)
if err == nil {
t.Fatalf("invalid endpoint %v", ep)
}
_ = c
}

View File

@@ -1,356 +0,0 @@
package static
import (
"errors"
"fmt"
"net/http"
"regexp"
"strings"
"sync"
"github.com/unistack-org/micro/v3/api"
"github.com/unistack-org/micro/v3/api/router"
"github.com/unistack-org/micro/v3/logger"
"github.com/unistack-org/micro/v3/metadata"
"github.com/unistack-org/micro/v3/registry"
rutil "github.com/unistack-org/micro/v3/util/registry"
util "github.com/unistack-org/micro/v3/util/router"
)
type endpoint struct {
apiep *api.Endpoint
hostregs []*regexp.Regexp
pathregs []util.Pattern
pcreregs []*regexp.Regexp
}
// router is the default router
type staticRouter struct {
exit chan bool
opts router.Options
sync.RWMutex
eps map[string]*endpoint
}
func (r *staticRouter) isClosed() bool {
select {
case <-r.exit:
return true
default:
return false
}
}
/*
// watch for endpoint changes
func (r *staticRouter) watch() {
var attempts int
for {
if r.isClosed() {
return
}
// watch for changes
w, err := r.opts.Registry.Watch()
if err != nil {
attempts++
log.Println("Error watching endpoints", err)
time.Sleep(time.Duration(attempts) * time.Second)
continue
}
ch := make(chan bool)
go func() {
select {
case <-ch:
w.Stop()
case <-r.exit:
w.Stop()
}
}()
// reset if we get here
attempts = 0
for {
// process next event
res, err := w.Next()
if err != nil {
log.Println("Error getting next endpoint", err)
close(ch)
break
}
r.process(res)
}
}
}
*/
func (r *staticRouter) Register(ep *api.Endpoint) error {
if err := api.Validate(ep); err != nil {
return err
}
var pathregs []util.Pattern
var hostregs []*regexp.Regexp
var pcreregs []*regexp.Regexp
for _, h := range ep.Host {
if h == "" || h == "*" {
continue
}
hostreg, err := regexp.CompilePOSIX(h)
if err != nil {
return err
}
hostregs = append(hostregs, hostreg)
}
for _, p := range ep.Path {
var pcreok bool
// pcre only when we have start and end markers
if p[0] == '^' && p[len(p)-1] == '$' {
pcrereg, err := regexp.CompilePOSIX(p)
if err == nil {
pcreregs = append(pcreregs, pcrereg)
pcreok = true
}
}
rule, err := util.Parse(p)
if err != nil && !pcreok {
return err
} else if err != nil && pcreok {
continue
}
tpl := rule.Compile()
pathreg, err := util.NewPattern(tpl.Version, tpl.OpCodes, tpl.Pool, "")
if err != nil {
return err
}
pathregs = append(pathregs, pathreg)
}
r.Lock()
r.eps[ep.Name] = &endpoint{
apiep: ep,
pcreregs: pcreregs,
pathregs: pathregs,
hostregs: hostregs,
}
r.Unlock()
return nil
}
func (r *staticRouter) Deregister(ep *api.Endpoint) error {
if err := api.Validate(ep); err != nil {
return err
}
r.Lock()
delete(r.eps, ep.Name)
r.Unlock()
return nil
}
func (r *staticRouter) Options() router.Options {
return r.opts
}
func (r *staticRouter) Close() error {
select {
case <-r.exit:
return nil
default:
close(r.exit)
}
return nil
}
func (r *staticRouter) Endpoint(req *http.Request) (*api.Service, error) {
ep, err := r.endpoint(req)
if err != nil {
return nil, err
}
epf := strings.Split(ep.apiep.Name, ".")
services, err := r.opts.Registry.GetService(epf[0])
if err != nil {
return nil, err
}
// hack for stream endpoint
if ep.apiep.Stream {
svcs := rutil.Copy(services)
for _, svc := range svcs {
if len(svc.Endpoints) == 0 {
e := &registry.Endpoint{}
e.Name = strings.Join(epf[1:], ".")
e.Metadata = make(map[string]string)
e.Metadata["stream"] = "true"
svc.Endpoints = append(svc.Endpoints, e)
}
for _, e := range svc.Endpoints {
e.Name = strings.Join(epf[1:], ".")
e.Metadata = make(map[string]string)
e.Metadata["stream"] = "true"
}
}
services = svcs
}
svc := &api.Service{
Name: epf[0],
Endpoint: &api.Endpoint{
Name: strings.Join(epf[1:], "."),
Handler: "rpc",
Host: ep.apiep.Host,
Method: ep.apiep.Method,
Path: ep.apiep.Path,
Body: ep.apiep.Body,
Stream: ep.apiep.Stream,
},
Services: services,
}
return svc, nil
}
func (r *staticRouter) endpoint(req *http.Request) (*endpoint, error) {
if r.isClosed() {
return nil, errors.New("router closed")
}
r.RLock()
defer r.RUnlock()
var idx int
if len(req.URL.Path) > 0 && req.URL.Path != "/" {
idx = 1
}
path := strings.Split(req.URL.Path[idx:], "/")
// use the first match
// TODO: weighted matching
for _, ep := range r.eps {
var mMatch, hMatch, pMatch bool
// 1. try method
for _, m := range ep.apiep.Method {
if m == req.Method {
mMatch = true
break
}
}
if !mMatch {
continue
}
if logger.V(logger.DebugLevel, logger.DefaultLogger) {
logger.Debugf("api method match %s", req.Method)
}
// 2. try host
if len(ep.apiep.Host) == 0 {
hMatch = true
} else {
for idx, h := range ep.apiep.Host {
if h == "" || h == "*" {
hMatch = true
break
} else {
if ep.hostregs[idx].MatchString(req.URL.Host) {
hMatch = true
break
}
}
}
}
if !hMatch {
continue
}
if logger.V(logger.DebugLevel, logger.DefaultLogger) {
logger.Debugf("api host match %s", req.URL.Host)
}
// 3. try google.api path
for _, pathreg := range ep.pathregs {
matches, err := pathreg.Match(path, "")
if err != nil {
if logger.V(logger.DebugLevel, logger.DefaultLogger) {
logger.Debugf("api gpath not match %s != %v", path, pathreg)
}
continue
}
if logger.V(logger.DebugLevel, logger.DefaultLogger) {
logger.Debugf("api gpath match %s = %v", path, pathreg)
}
pMatch = true
ctx := req.Context()
md, ok := metadata.FromContext(ctx)
if !ok {
md = make(metadata.Metadata)
}
for k, v := range matches {
md[fmt.Sprintf("x-api-field-%s", k)] = v
}
md["x-api-body"] = ep.apiep.Body
*req = *req.Clone(metadata.NewContext(ctx, md))
break
}
if !pMatch {
// 4. try path via pcre path matching
for _, pathreg := range ep.pcreregs {
if !pathreg.MatchString(req.URL.Path) {
if logger.V(logger.DebugLevel, logger.DefaultLogger) {
logger.Debugf("api pcre path not match %s != %v", req.URL.Path, pathreg)
}
continue
}
pMatch = true
break
}
}
if !pMatch {
continue
}
// TODO: Percentage traffic
// we got here, so its a match
return ep, nil
}
// no match
return nil, fmt.Errorf("endpoint not found for %v", req.URL)
}
func (r *staticRouter) Route(req *http.Request) (*api.Service, error) {
if r.isClosed() {
return nil, errors.New("router closed")
}
// try get an endpoint
ep, err := r.Endpoint(req)
if err != nil {
return nil, err
}
return ep, nil
}
func NewRouter(opts ...router.Option) *staticRouter {
options := router.NewOptions(opts...)
r := &staticRouter{
exit: make(chan bool),
opts: options,
eps: make(map[string]*endpoint),
}
//go r.watch()
//go r.refresh()
return r
}

View File

@@ -15,6 +15,7 @@ var (
// Provider is a ACME provider interface // Provider is a ACME provider interface
type Provider interface { type Provider interface {
Init(...Option) error
// Listen returns a new listener // Listen returns a new listener
Listen(...string) (net.Listener, error) Listen(...string) (net.Listener, error)
// TLSConfig returns a tls config // TLSConfig returns a tls config

View File

@@ -15,6 +15,10 @@ import (
// autoCertACME is the ACME provider from golang.org/x/crypto/acme/autocert // autoCertACME is the ACME provider from golang.org/x/crypto/acme/autocert
type autocertProvider struct{} type autocertProvider struct{}
func (a *autocertProvider) Init(opts ...acme.Option) error {
return nil
}
// Listen implements acme.Provider // Listen implements acme.Provider
func (a *autocertProvider) Listen(hosts ...string) (net.Listener, error) { func (a *autocertProvider) Listen(hosts ...string) (net.Listener, error) {
return autocert.NewListener(hosts...), nil return autocert.NewListener(hosts...), nil
@@ -31,8 +35,8 @@ func (a *autocertProvider) TLSConfig(hosts ...string) (*tls.Config, error) {
} }
dir := cacheDir() dir := cacheDir()
if err := os.MkdirAll(dir, 0700); err != nil { if err := os.MkdirAll(dir, 0700); err != nil {
if logger.V(logger.InfoLevel, logger.DefaultLogger) { if logger.V(logger.InfoLevel) {
logger.Infof("warning: autocert not using a cache: %v", err) logger.Info("warning: autocert not using a cache: %v", err)
} }
} else { } else {
m.Cache = autocert.DirCache(dir) m.Cache = autocert.DirCache(dir)

View File

@@ -3,13 +3,13 @@ package certmagic
import ( import (
"crypto/tls" "crypto/tls"
"fmt"
"math/rand" "math/rand"
"net" "net"
"time" "time"
"github.com/caddyserver/certmagic" "github.com/caddyserver/certmagic"
"github.com/unistack-org/micro/v3/api/server/acme" "github.com/unistack-org/micro/v3/api/server/acme"
"github.com/unistack-org/micro/v3/logger"
) )
type certmagicProvider struct { type certmagicProvider struct {
@@ -48,6 +48,15 @@ func (c *certmagicProvider) TLSConfig(hosts ...string) (*tls.Config, error) {
return certmagic.TLS(hosts) return certmagic.TLS(hosts)
} }
func (p *certmagicProvider) Init(opts ...acme.Option) error {
if p.opts.Cache != nil {
if _, ok := p.opts.Cache.(certmagic.Storage); !ok {
return fmt.Errorf("ACME: cache provided doesn't implement certmagic's Storage interface")
}
}
return nil
}
// NewProvider returns a certmagic provider // NewProvider returns a certmagic provider
func NewProvider(options ...acme.Option) acme.Provider { func NewProvider(options ...acme.Option) acme.Provider {
opts := acme.DefaultOptions() opts := acme.DefaultOptions()
@@ -56,12 +65,6 @@ func NewProvider(options ...acme.Option) acme.Provider {
o(&opts) o(&opts)
} }
if opts.Cache != nil {
if _, ok := opts.Cache.(certmagic.Storage); !ok {
logger.Fatal("ACME: cache provided doesn't implement certmagic's Storage interface")
}
}
return &certmagicProvider{ return &certmagicProvider{
opts: opts, opts: opts,
} }

View File

@@ -52,14 +52,14 @@ func (s *storage) Store(key string, value []byte) error {
Key: key, Key: key,
Value: buf.Bytes(), Value: buf.Bytes(),
} }
return s.store.Write(r) return s.store.Write(s.store.Options().Context, r)
} }
func (s *storage) Load(key string) ([]byte, error) { func (s *storage) Load(key string) ([]byte, error) {
if !s.Exists(key) { if !s.Exists(key) {
return nil, certmagic.ErrNotExist(errors.New(key + " doesn't exist")) return nil, certmagic.ErrNotExist(errors.New(key + " doesn't exist"))
} }
records, err := s.store.Read(key) records, err := s.store.Read(s.store.Options().Context, key)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -77,18 +77,18 @@ func (s *storage) Load(key string) ([]byte, error) {
} }
func (s *storage) Delete(key string) error { func (s *storage) Delete(key string) error {
return s.store.Delete(key) return s.store.Delete(s.store.Options().Context, key)
} }
func (s *storage) Exists(key string) bool { func (s *storage) Exists(key string) bool {
if _, err := s.store.Read(key); err != nil { if _, err := s.store.Read(s.store.Options().Context, key); err != nil {
return false return false
} }
return true return true
} }
func (s *storage) List(prefix string, recursive bool) ([]string, error) { func (s *storage) List(prefix string, recursive bool) ([]string, error) {
keys, err := s.store.List() keys, err := s.store.List(s.store.Options().Context)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -116,7 +116,7 @@ func (s *storage) List(prefix string, recursive bool) ([]string, error) {
} }
func (s *storage) Stat(key string) (certmagic.KeyInfo, error) { func (s *storage) Stat(key string) (certmagic.KeyInfo, error) {
records, err := s.store.Read(key) records, err := s.store.Read(s.store.Options().Context, key)
if err != nil { if err != nil {
return certmagic.KeyInfo{}, err return certmagic.KeyInfo{}, err
} }

View File

@@ -15,19 +15,14 @@ type httpServer struct {
mux *http.ServeMux mux *http.ServeMux
opts server.Options opts server.Options
mtx sync.RWMutex sync.RWMutex
address string address string
exit chan chan error exit chan chan error
} }
func NewServer(address string, opts ...server.Option) server.Server { func NewServer(address string, opts ...server.Option) server.Server {
var options server.Options
for _, o := range opts {
o(&options)
}
return &httpServer{ return &httpServer{
opts: options, opts: server.NewOptions(opts...),
mux: http.NewServeMux(), mux: http.NewServeMux(),
address: address, address: address,
exit: make(chan chan error), exit: make(chan chan error),
@@ -35,8 +30,8 @@ func NewServer(address string, opts ...server.Option) server.Server {
} }
func (s *httpServer) Address() string { func (s *httpServer) Address() string {
s.mtx.RLock() s.RLock()
defer s.mtx.RUnlock() defer s.RUnlock()
return s.address return s.address
} }
@@ -62,6 +57,9 @@ func (s *httpServer) Start() error {
var l net.Listener var l net.Listener
var err error var err error
s.RLock()
config := s.opts
s.RUnlock()
if s.opts.EnableACME && s.opts.ACMEProvider != nil { if s.opts.EnableACME && s.opts.ACMEProvider != nil {
// should we check the address to make sure its using :443? // should we check the address to make sure its using :443?
l, err = s.opts.ACMEProvider.Listen(s.opts.ACMEHosts...) l, err = s.opts.ACMEProvider.Listen(s.opts.ACMEHosts...)
@@ -75,18 +73,21 @@ func (s *httpServer) Start() error {
return err return err
} }
if logger.V(logger.InfoLevel, logger.DefaultLogger) { if config.Logger.V(logger.InfoLevel) {
logger.Infof("HTTP API Listening on %s", l.Addr().String()) config.Logger.Info("HTTP API Listening on %s", l.Addr().String())
} }
s.mtx.Lock() s.Lock()
s.address = l.Addr().String() s.address = l.Addr().String()
s.mtx.Unlock() s.Unlock()
go func() { go func() {
if err := http.Serve(l, s.mux); err != nil { if err := http.Serve(l, s.mux); err != nil {
// temporary fix // temporary fix
logger.Error(err) if config.Logger.V(logger.ErrorLevel) {
config.Logger.Error("serve err: %v", err)
}
s.Stop()
} }
}() }()

View File

@@ -6,10 +6,13 @@ import (
"github.com/unistack-org/micro/v3/api/resolver" "github.com/unistack-org/micro/v3/api/resolver"
"github.com/unistack-org/micro/v3/api/server/acme" "github.com/unistack-org/micro/v3/api/server/acme"
"github.com/unistack-org/micro/v3/logger"
) )
// Option func
type Option func(o *Options) type Option func(o *Options)
// Options for api server
type Options struct { type Options struct {
EnableACME bool EnableACME bool
EnableCORS bool EnableCORS bool
@@ -19,6 +22,18 @@ type Options struct {
TLSConfig *tls.Config TLSConfig *tls.Config
Resolver resolver.Resolver Resolver resolver.Resolver
Wrappers []Wrapper Wrappers []Wrapper
Logger logger.Logger
}
// NewOptions returns new Options
func NewOptions(opts ...Option) Options {
options := Options{
Logger: logger.DefaultLogger,
}
for _, o := range opts {
o(&options)
}
return options
} }
type Wrapper func(h http.Handler) http.Handler type Wrapper func(h http.Handler) http.Handler
@@ -70,3 +85,9 @@ func Resolver(r resolver.Resolver) Option {
o.Resolver = r o.Resolver = r
} }
} }
func Logger(l logger.Logger) Option {
return func(o *Options) {
o.Logger = l
}
}

View File

@@ -17,7 +17,7 @@ const (
) )
var ( var (
DefaultAuth Auth DefaultAuth Auth = &NoopAuth{opts: NewOptions()}
// ErrInvalidToken is when the token provided is not valid // ErrInvalidToken is when the token provided is not valid
ErrInvalidToken = errors.New("invalid token provided") ErrInvalidToken = errors.New("invalid token provided")
// ErrForbidden is when a user does not have the necessary scope to access a resource // ErrForbidden is when a user does not have the necessary scope to access a resource

View File

@@ -1,151 +0,0 @@
// Package jwt is a jwt implementation of the auth interface
package jwt
import (
"sync"
"time"
"github.com/unistack-org/micro/v3/auth"
"github.com/unistack-org/micro/v3/util/token"
"github.com/unistack-org/micro/v3/util/token/jwt"
)
// NewAuth returns a new instance of the Auth service
func NewAuth(opts ...auth.Option) auth.Auth {
j := new(jwtAuth)
j.Init(opts...)
return j
}
type jwtAuth struct {
options auth.Options
token token.Provider
rules []*auth.Rule
sync.Mutex
}
func (j *jwtAuth) String() string {
return "jwt"
}
func (j *jwtAuth) Init(opts ...auth.Option) {
j.Lock()
defer j.Unlock()
for _, o := range opts {
o(&j.options)
}
j.token = jwt.NewTokenProvider(
token.WithPrivateKey(j.options.PrivateKey),
token.WithPublicKey(j.options.PublicKey),
)
}
func (j *jwtAuth) Options() auth.Options {
j.Lock()
defer j.Unlock()
return j.options
}
func (j *jwtAuth) Generate(id string, opts ...auth.GenerateOption) (*auth.Account, error) {
options := auth.NewGenerateOptions(opts...)
if len(options.Issuer) == 0 {
options.Issuer = j.Options().Issuer
}
account := &auth.Account{
ID: id,
Type: options.Type,
Scopes: options.Scopes,
Metadata: options.Metadata,
Issuer: options.Issuer,
}
// generate a JWT secret which can be provided to the Token() method
// and exchanged for an access token
secret, err := j.token.Generate(account, token.WithExpiry(time.Hour*24*365))
if err != nil {
return nil, err
}
account.Secret = secret.Token
// return the account
return account, nil
}
func (j *jwtAuth) Grant(rule *auth.Rule) error {
j.Lock()
defer j.Unlock()
j.rules = append(j.rules, rule)
return nil
}
func (j *jwtAuth) Revoke(rule *auth.Rule) error {
j.Lock()
defer j.Unlock()
rules := []*auth.Rule{}
for _, r := range j.rules {
if r.ID != rule.ID {
rules = append(rules, r)
}
}
j.rules = rules
return nil
}
func (j *jwtAuth) Verify(acc *auth.Account, res *auth.Resource, opts ...auth.VerifyOption) error {
j.Lock()
defer j.Unlock()
var options auth.VerifyOptions
for _, o := range opts {
o(&options)
}
return auth.VerifyAccess(j.rules, acc, res)
}
func (j *jwtAuth) Rules(opts ...auth.RulesOption) ([]*auth.Rule, error) {
j.Lock()
defer j.Unlock()
return j.rules, nil
}
func (j *jwtAuth) Inspect(token string) (*auth.Account, error) {
return j.token.Inspect(token)
}
func (j *jwtAuth) Token(opts ...auth.TokenOption) (*auth.Token, error) {
options := auth.NewTokenOptions(opts...)
secret := options.RefreshToken
if len(options.Secret) > 0 {
secret = options.Secret
}
account, err := j.token.Inspect(secret)
if err != nil {
return nil, err
}
access, err := j.token.Generate(account, token.WithExpiry(options.Expiry))
if err != nil {
return nil, err
}
refresh, err := j.token.Generate(account, token.WithExpiry(options.Expiry+time.Hour))
if err != nil {
return nil, err
}
return &auth.Token{
Created: access.Created,
Expiry: access.Expiry,
AccessToken: access.Token,
RefreshToken: refresh.Token,
}, nil
}

73
auth/noop.go Normal file
View File

@@ -0,0 +1,73 @@
package auth
import (
"github.com/google/uuid"
)
type NoopAuth struct {
opts Options
}
// String returns the name of the implementation
func (n *NoopAuth) String() string {
return "noop"
}
// Init the auth
func (n *NoopAuth) Init(opts ...Option) {
for _, o := range opts {
o(&n.opts)
}
}
// Options set for auth
func (n *NoopAuth) Options() Options {
return n.opts
}
// Generate a new account
func (n *NoopAuth) Generate(id string, opts ...GenerateOption) (*Account, error) {
options := NewGenerateOptions(opts...)
return &Account{
ID: id,
Secret: options.Secret,
Metadata: options.Metadata,
Scopes: options.Scopes,
Issuer: n.Options().Issuer,
}, nil
}
// Grant access to a resource
func (n *NoopAuth) Grant(rule *Rule) error {
return nil
}
// Revoke access to a resource
func (n *NoopAuth) Revoke(rule *Rule) error {
return nil
}
// Rules used to verify requests
func (n *NoopAuth) Rules(opts ...RulesOption) ([]*Rule, error) {
return []*Rule{}, nil
}
// Verify an account has access to a resource
func (n *NoopAuth) Verify(acc *Account, res *Resource, opts ...VerifyOption) error {
return nil
}
// Inspect a token
func (n *NoopAuth) Inspect(token string) (*Account, error) {
uid, err := uuid.NewRandom()
if err != nil {
return nil, err
}
return &Account{ID: uid.String(), Issuer: n.Options().Issuer}, nil
}
// Token generation using an account id and secret
func (n *NoopAuth) Token(opts ...TokenOption) (*Token, error) {
return &Token{}, nil
}

View File

@@ -1,81 +0,0 @@
package noop
import (
"github.com/google/uuid"
"github.com/unistack-org/micro/v3/auth"
)
func NewAuth(opts ...auth.Option) auth.Auth {
var options auth.Options
for _, o := range opts {
o(&options)
}
return &noop{
opts: options,
}
}
type noop struct {
opts auth.Options
}
// String returns the name of the implementation
func (n *noop) String() string {
return "noop"
}
// Init the auth
func (n *noop) Init(opts ...auth.Option) {
for _, o := range opts {
o(&n.opts)
}
}
// Options set for auth
func (n *noop) Options() auth.Options {
return n.opts
}
// Generate a new account
func (n *noop) Generate(id string, opts ...auth.GenerateOption) (*auth.Account, error) {
options := auth.NewGenerateOptions(opts...)
return &auth.Account{
ID: id,
Secret: options.Secret,
Metadata: options.Metadata,
Scopes: options.Scopes,
Issuer: n.Options().Issuer,
}, nil
}
// Grant access to a resource
func (n *noop) Grant(rule *auth.Rule) error {
return nil
}
// Revoke access to a resource
func (n *noop) Revoke(rule *auth.Rule) error {
return nil
}
// Rules used to verify requests
func (n *noop) Rules(opts ...auth.RulesOption) ([]*auth.Rule, error) {
return []*auth.Rule{}, nil
}
// Verify an account has access to a resource
func (n *noop) Verify(acc *auth.Account, res *auth.Resource, opts ...auth.VerifyOption) error {
return nil
}
// Inspect a token
func (n *noop) Inspect(token string) (*auth.Account, error) {
return &auth.Account{ID: uuid.New().String(), Issuer: n.Options().Issuer}, nil
}
// Token generation using an account id and secret
func (n *noop) Token(opts ...auth.TokenOption) (*auth.Token, error) {
return &auth.Token{}, nil
}

View File

@@ -1,8 +1,10 @@
// Package broker is an interface used for asynchronous messaging // Package broker is an interface used for asynchronous messaging
package broker package broker
import "context"
var ( var (
DefaultBroker Broker = newBroker() DefaultBroker Broker = NewBroker()
) )
// Broker is an interface used for asynchronous messaging. // Broker is an interface used for asynchronous messaging.
@@ -10,10 +12,10 @@ type Broker interface {
Init(...Option) error Init(...Option) error
Options() Options Options() Options
Address() string Address() string
Connect() error Connect(context.Context) error
Disconnect() error Disconnect(context.Context) error
Publish(topic string, m *Message, opts ...PublishOption) error Publish(context.Context, string, *Message, ...PublishOption) error
Subscribe(topic string, h Handler, opts ...SubscribeOption) (Subscriber, error) Subscribe(context.Context, string, Handler, ...SubscribeOption) (Subscriber, error)
String() string String() string
} }
@@ -30,14 +32,13 @@ type Event interface {
// Message is used to transfer data // Message is used to transfer data
type Message struct { type Message struct {
Header map[string]string Header map[string]string // contains message metadata
Body []byte Body []byte // contains message body
Error error
} }
// Subscriber is a convenience return type for the Subscribe method // Subscriber is a convenience return type for the Subscribe method
type Subscriber interface { type Subscriber interface {
Options() SubscribeOptions Options() SubscribeOptions
Topic() string Topic() string
Unsubscribe() error Unsubscribe(context.Context) error
} }

46
broker/context.go Normal file
View File

@@ -0,0 +1,46 @@
package broker
import (
"context"
)
type brokerKey struct{}
func FromContext(ctx context.Context) (Broker, bool) {
c, ok := ctx.Value(brokerKey{}).(Broker)
return c, ok
}
func NewContext(ctx context.Context, s Broker) context.Context {
return context.WithValue(ctx, brokerKey{}, s)
}
// SetSubscribeOption returns a function to setup a context with given value
func SetSubscribeOption(k, v interface{}) SubscribeOption {
return func(o *SubscribeOptions) {
if o.Context == nil {
o.Context = context.Background()
}
o.Context = context.WithValue(o.Context, k, v)
}
}
// 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)
}
}
// SetPublishOption returns a function to setup a context with given value
func SetPublishOption(k, v interface{}) PublishOption {
return func(o *PublishOptions) {
if o.Context == nil {
o.Context = context.Background()
}
o.Context = context.WithValue(o.Context, k, v)
}
}

View File

@@ -1,5 +1,7 @@
package broker package broker
import "context"
type noopBroker struct { type noopBroker struct {
opts Options opts Options
} }
@@ -9,6 +11,12 @@ type noopSubscriber struct {
opts SubscribeOptions opts SubscribeOptions
} }
// NewBroker returns new noop broker
func NewBroker(opts ...Option) Broker {
return &noopBroker{opts: NewOptions(opts...)}
}
// Init initialize broker
func (n *noopBroker) Init(opts ...Option) error { func (n *noopBroker) Init(opts ...Option) error {
for _, o := range opts { for _, o := range opts {
o(&n.opts) o(&n.opts)
@@ -17,58 +25,53 @@ func (n *noopBroker) Init(opts ...Option) error {
return nil return nil
} }
// Options returns broker Options
func (n *noopBroker) Options() Options { func (n *noopBroker) Options() Options {
return n.opts return n.opts
} }
// Address returns broker address
func (n *noopBroker) Address() string { func (n *noopBroker) Address() string {
return "" return ""
} }
func (n *noopBroker) Connect() error { // Connect connects to broker
func (n *noopBroker) Connect(ctx context.Context) error {
return nil return nil
} }
func (n *noopBroker) Disconnect() error { // Disconnect disconnects from broker
func (n *noopBroker) Disconnect(ctx context.Context) error {
return nil return nil
} }
func (n *noopBroker) Publish(topic string, m *Message, opts ...PublishOption) error { // Publish publishes message to broker
func (n *noopBroker) Publish(ctx context.Context, topic string, m *Message, opts ...PublishOption) error {
return nil return nil
} }
func (n *noopBroker) Subscribe(topic string, h Handler, opts ...SubscribeOption) (Subscriber, error) { // Subscribe subscribes to broker topic
options := NewSubscribeOptions() func (n *noopBroker) Subscribe(ctx context.Context, topic string, h Handler, opts ...SubscribeOption) (Subscriber, error) {
options := NewSubscribeOptions(opts...)
for _, o := range opts {
o(&options)
}
return &noopSubscriber{topic: topic, opts: options}, nil return &noopSubscriber{topic: topic, opts: options}, nil
} }
// String return broker string representation
func (n *noopBroker) String() string { func (n *noopBroker) String() string {
return "noop" return "noop"
} }
// Options returns subscriber options
func (n *noopSubscriber) Options() SubscribeOptions { func (n *noopSubscriber) Options() SubscribeOptions {
return n.opts return n.opts
} }
// TOpic returns subscriber topic
func (n *noopSubscriber) Topic() string { func (n *noopSubscriber) Topic() string {
return n.topic return n.topic
} }
func (n *noopSubscriber) Unsubscribe() error { // Unsubscribe unsbscribes from broker topic
func (n *noopSubscriber) Unsubscribe(ctx context.Context) error {
return nil return nil
} }
// newBroker returns a new noop broker
func newBroker(opts ...Option) Broker {
options := NewOptions()
for _, o := range opts {
o(&options)
}
return &noopBroker{opts: options}
}

View File

@@ -9,6 +9,7 @@ import (
"github.com/unistack-org/micro/v3/registry" "github.com/unistack-org/micro/v3/registry"
) )
// Options struct
type Options struct { type Options struct {
Addrs []string Addrs []string
Secure bool Secure bool
@@ -27,18 +28,47 @@ type Options struct {
Context context.Context Context context.Context
} }
func NewOptions() Options { // NewOptions create new Options
return Options{ func NewOptions(opts ...Option) Options {
Context: context.Background(), options := Options{
Registry: registry.DefaultRegistry,
Logger: logger.DefaultLogger,
Context: context.Background(),
}
for _, o := range opts {
o(&options)
}
return options
}
// Context sets the context option
func Context(ctx context.Context) Option {
return func(o *Options) {
o.Context = ctx
} }
} }
// PublishOptions struct
type PublishOptions struct { type PublishOptions struct {
// Other options for implementations of the interface // Other options for implementations of the interface
// can be stored in a context // can be stored in a context
Context context.Context Context context.Context
} }
// NewPublishOptions creates PublishOptions struct
func NewPublishOptions(opts ...PublishOption) PublishOptions {
options := PublishOptions{
Context: context.Background(),
}
for _, o := range opts {
o(&options)
}
return options
}
// SubscribeOptions struct
type SubscribeOptions struct { type SubscribeOptions struct {
// AutoAck ack messages if handler returns nil err // AutoAck ack messages if handler returns nil err
AutoAck bool AutoAck bool
@@ -56,30 +86,34 @@ type SubscribeOptions struct {
Context context.Context Context context.Context
} }
// Option func
type Option func(*Options) type Option func(*Options)
// PublishOption func
type PublishOption func(*PublishOptions) type PublishOption func(*PublishOptions)
// PublishContext set context // PublishContext sets the context
func PublishContext(ctx context.Context) PublishOption { func PublishContext(ctx context.Context) PublishOption {
return func(o *PublishOptions) { return func(o *PublishOptions) {
o.Context = ctx o.Context = ctx
} }
} }
// SubscribeOption func
type SubscribeOption func(*SubscribeOptions) type SubscribeOption func(*SubscribeOptions)
// NewSubscribeOptions creates new SubscribeOptions
func NewSubscribeOptions(opts ...SubscribeOption) SubscribeOptions { func NewSubscribeOptions(opts ...SubscribeOption) SubscribeOptions {
opt := SubscribeOptions{ options := SubscribeOptions{
AutoAck: true, AutoAck: true,
Context: context.Background(), Context: context.Background(),
} }
for _, o := range opts { for _, o := range opts {
o(&opt) o(&options)
} }
return opt return options
} }
// Addrs sets the host addresses to be used by the broker // Addrs sets the host addresses to be used by the broker
@@ -97,6 +131,7 @@ func Codec(c codec.Marshaler) Option {
} }
} }
// DisableAutoAck disables auto ack
func DisableAutoAck() SubscribeOption { func DisableAutoAck() SubscribeOption {
return func(o *SubscribeOptions) { return func(o *SubscribeOptions) {
o.AutoAck = false o.AutoAck = false
@@ -127,6 +162,7 @@ func SubscribeErrorHandler(h Handler) SubscribeOption {
} }
} }
// Queue sets the subscribers sueue
func Queue(name string) SubscribeOption { func Queue(name string) SubscribeOption {
return func(o *SubscribeOptions) { return func(o *SubscribeOptions) {
o.Group = name o.Group = name
@@ -140,6 +176,7 @@ func SubscribeGroup(name string) SubscribeOption {
} }
} }
// Registry sets registry option
func Registry(r registry.Registry) Option { func Registry(r registry.Registry) Option {
return func(o *Options) { return func(o *Options) {
o.Registry = r o.Registry = r
@@ -153,7 +190,7 @@ func Secure(b bool) Option {
} }
} }
// Specify TLS Config // TLSConfig sets the TLS Config
func TLSConfig(t *tls.Config) Option { func TLSConfig(t *tls.Config) Option {
return func(o *Options) { return func(o *Options) {
o.TLSConfig = t o.TLSConfig = t

View File

@@ -1,13 +1,15 @@
package build package build
// Options struct
type Options struct { type Options struct {
// local path to download source // local path to download source
Path string Path string
} }
// Option func
type Option func(o *Options) type Option func(o *Options)
// Local path for repository // Path is the Local path for repository
func Path(p string) Option { func Path(p string) Option {
return func(o *Options) { return func(o *Options) {
o.Path = p o.Path = p

29
cache/cache.go vendored
View File

@@ -1,29 +0,0 @@
// Package cache is a caching interface
package cache
// Cache is an interface for caching
type Cache interface {
// Initialise options
Init(...Option) error
// Get a value
Get(key string) (interface{}, error)
// Set a value
Set(key string, val interface{}) error
// Delete a value
Delete(key string) error
// Name of the implementation
String() string
}
type Options struct {
Nodes []string
}
type Option func(o *Options)
// Nodes sets the nodes for the cache
func Nodes(v ...string) Option {
return func(o *Options) {
o.Nodes = v
}
}

View File

@@ -7,6 +7,7 @@ import (
"github.com/unistack-org/micro/v3/util/backoff" "github.com/unistack-org/micro/v3/util/backoff"
) )
// BackoffFunc is the backoff call func
type BackoffFunc func(ctx context.Context, req Request, attempts int) (time.Duration, error) type BackoffFunc func(ctx context.Context, req Request, attempts int) (time.Duration, error)
func exponentialBackoff(ctx context.Context, req Request, attempts int) (time.Duration, error) { func exponentialBackoff(ctx context.Context, req Request, attempts int) (time.Duration, error) {

View File

@@ -1,66 +0,0 @@
package client
import (
"context"
"encoding/json"
"fmt"
"hash/fnv"
"time"
cache "github.com/patrickmn/go-cache"
"github.com/unistack-org/micro/v3/metadata"
)
// NewCache returns an initialised cache.
func NewCache() *Cache {
return &Cache{
cache: cache.New(cache.NoExpiration, 30*time.Second),
}
}
// Cache for responses
type Cache struct {
cache *cache.Cache
}
// Get a response from the cache
func (c *Cache) Get(ctx context.Context, req Request) (interface{}, bool) {
return c.cache.Get(key(ctx, req))
}
// Set a response in the cache
func (c *Cache) Set(ctx context.Context, req Request, rsp interface{}, expiry time.Duration) {
c.cache.Set(key(ctx, req), rsp, expiry)
}
// List the key value pairs in the cache
func (c *Cache) List() map[string]string {
items := c.cache.Items()
rsp := make(map[string]string, len(items))
for k, v := range items {
bytes, _ := json.Marshal(v.Object)
rsp[k] = string(bytes)
}
return rsp
}
// key returns a hash for the context and request
func key(ctx context.Context, req Request) string {
ns, _ := metadata.Get(ctx, "Micro-Namespace")
bytes, _ := json.Marshal(map[string]interface{}{
"namespace": ns,
"request": map[string]interface{}{
"service": req.Service(),
"endpoint": req.Endpoint(),
"method": req.Method(),
"body": req.Body(),
},
})
h := fnv.New64()
h.Write(bytes)
return fmt.Sprintf("%x", h.Sum(nil))
}

View File

@@ -1,77 +0,0 @@
package client
import (
"context"
"testing"
"time"
"github.com/unistack-org/micro/v3/metadata"
)
func TestCache(t *testing.T) {
ctx := context.TODO()
req := &testRequest{service: "go.micro.service.foo", method: "Foo.Bar"}
t.Run("CacheMiss", func(t *testing.T) {
if _, ok := NewCache().Get(ctx, req); ok {
t.Errorf("Expected to get no result from Get")
}
})
t.Run("CacheHit", func(t *testing.T) {
c := NewCache()
rsp := "theresponse"
c.Set(ctx, req, rsp, time.Minute)
if res, ok := c.Get(ctx, req); !ok {
t.Errorf("Expected a result, got nothing")
} else if res != rsp {
t.Errorf("Expected '%v' result, got '%v'", rsp, res)
}
})
}
func TestCacheKey(t *testing.T) {
ctx := context.TODO()
req1 := &testRequest{service: "go.micro.service.foo", method: "Foo.Bar"}
req2 := &testRequest{service: "go.micro.service.foo", method: "Foo.Baz"}
req3 := &testRequest{service: "go.micro.service.foo", method: "Foo.Bar", body: "customquery"}
t.Run("IdenticalRequests", func(t *testing.T) {
key1 := key(ctx, req1)
key2 := key(ctx, req1)
if key1 != key2 {
t.Errorf("Expected the keys to match for identical requests and context")
}
})
t.Run("DifferentRequestEndpoints", func(t *testing.T) {
key1 := key(ctx, req1)
key2 := key(ctx, req2)
if key1 == key2 {
t.Errorf("Expected the keys to differ for different request endpoints")
}
})
t.Run("DifferentRequestBody", func(t *testing.T) {
key1 := key(ctx, req2)
key2 := key(ctx, req3)
if key1 == key2 {
t.Errorf("Expected the keys to differ for different request bodies")
}
})
t.Run("DifferentMetadata", func(t *testing.T) {
mdCtx := metadata.Set(context.TODO(), "Micro-Namespace", "bar")
key1 := key(mdCtx, req1)
key2 := key(ctx, req1)
if key1 == key2 {
t.Errorf("Expected the keys to differ for different metadata")
}
})
}

View File

@@ -9,7 +9,8 @@ import (
) )
var ( var (
DefaultClient Client // DefaultClient is the global default client
DefaultClient Client = NewClient()
) )
// Client is the interface used to make requests to services. // Client is the interface used to make requests to services.

View File

@@ -14,3 +14,13 @@ func FromContext(ctx context.Context) (Client, bool) {
func NewContext(ctx context.Context, c Client) context.Context { func NewContext(ctx context.Context, c Client) context.Context {
return context.WithValue(ctx, clientKey{}, c) return context.WithValue(ctx, clientKey{}, c)
} }
// SetPublishOption returns a function to setup a context with given value
func SetPublishOption(k, v interface{}) PublishOption {
return func(o *PublishOptions) {
if o.Context == nil {
o.Context = context.Background()
}
o.Context = context.WithValue(o.Context, k, v)
}
}

205
client/noop.go Normal file
View File

@@ -0,0 +1,205 @@
package client
import (
"context"
raw "github.com/unistack-org/micro-codec-bytes"
json "github.com/unistack-org/micro-codec-json"
"github.com/unistack-org/micro/v3/broker"
"github.com/unistack-org/micro/v3/codec"
"github.com/unistack-org/micro/v3/errors"
"github.com/unistack-org/micro/v3/metadata"
)
type noopClient struct {
opts Options
}
type noopMessage struct {
topic string
payload interface{}
opts MessageOptions
}
type noopRequest struct {
service string
method string
endpoint string
contentType string
body interface{}
codec codec.Writer
stream bool
}
// NewClient returns new noop client
func NewClient(opts ...Option) Client {
return &noopClient{opts: NewOptions(opts...)}
}
func (n *noopRequest) Service() string {
return n.service
}
func (n *noopRequest) Method() string {
return n.method
}
func (n *noopRequest) Endpoint() string {
return n.endpoint
}
func (n *noopRequest) ContentType() string {
return n.contentType
}
func (n *noopRequest) Body() interface{} {
return n.body
}
func (n *noopRequest) Codec() codec.Writer {
return n.codec
}
func (n *noopRequest) Stream() bool {
return n.stream
}
type noopResponse struct {
codec codec.Reader
header map[string]string
}
func (n *noopResponse) Codec() codec.Reader {
return n.codec
}
func (n *noopResponse) Header() map[string]string {
return n.header
}
func (n *noopResponse) Read() ([]byte, error) {
return nil, nil
}
type noopStream struct{}
func (n *noopStream) Context() context.Context {
return context.Background()
}
func (n *noopStream) Request() Request {
return &noopRequest{}
}
func (n *noopStream) Response() Response {
return &noopResponse{}
}
func (n *noopStream) Send(interface{}) error {
return nil
}
func (n *noopStream) Recv(interface{}) error {
return nil
}
func (n *noopStream) Error() error {
return nil
}
func (n *noopStream) Close() error {
return nil
}
func (n *noopMessage) Topic() string {
return n.topic
}
func (n *noopMessage) Payload() interface{} {
return n.payload
}
func (n *noopMessage) ContentType() string {
return n.opts.ContentType
}
func (n *noopClient) Init(opts ...Option) error {
for _, o := range opts {
o(&n.opts)
}
return nil
}
func (n *noopClient) Options() Options {
return n.opts
}
func (n *noopClient) String() string {
return "noop"
}
func (n *noopClient) Call(ctx context.Context, req Request, rsp interface{}, opts ...CallOption) error {
return nil
}
func (n *noopClient) NewRequest(service, endpoint string, req interface{}, opts ...RequestOption) Request {
return &noopRequest{}
}
func (n *noopClient) NewMessage(topic string, msg interface{}, opts ...MessageOption) Message {
options := NewMessageOptions(opts...)
return &noopMessage{topic: topic, payload: msg, opts: options}
}
func (n *noopClient) Stream(ctx context.Context, req Request, opts ...CallOption) (Stream, error) {
return &noopStream{}, nil
}
func (n *noopClient) Publish(ctx context.Context, p Message, opts ...PublishOption) error {
var body []byte
options := NewPublishOptions(opts...)
md, ok := metadata.FromContext(ctx)
if !ok {
md = metadata.New(0)
}
md["Content-Type"] = p.ContentType()
md["Micro-Topic"] = p.Topic()
// passed in raw data
if d, ok := p.Payload().(*raw.Frame); ok {
body = d.Data
} else {
cf := n.opts.Broker.Options().Codec
if cf == nil {
cf = json.Marshaler{}
}
/*
// use codec for payload
cf, err := n.opts.Codecs[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
}
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))
}

View File

@@ -7,11 +7,11 @@ import (
"github.com/unistack-org/micro/v3/broker" "github.com/unistack-org/micro/v3/broker"
"github.com/unistack-org/micro/v3/codec" "github.com/unistack-org/micro/v3/codec"
"github.com/unistack-org/micro/v3/logger" "github.com/unistack-org/micro/v3/logger"
"github.com/unistack-org/micro/v3/network/transport"
"github.com/unistack-org/micro/v3/registry" "github.com/unistack-org/micro/v3/registry"
"github.com/unistack-org/micro/v3/router" "github.com/unistack-org/micro/v3/router"
"github.com/unistack-org/micro/v3/selector" "github.com/unistack-org/micro/v3/selector"
"github.com/unistack-org/micro/v3/selector/random" "github.com/unistack-org/micro/v3/selector/random"
"github.com/unistack-org/micro/v3/transport"
) )
type Options struct { type Options struct {
@@ -34,9 +34,6 @@ type Options struct {
PoolSize int PoolSize int
PoolTTL time.Duration PoolTTL time.Duration
// Response cache
Cache *Cache
// Middleware for client // Middleware for client
Wrappers []Wrapper Wrappers []Wrapper
@@ -48,13 +45,19 @@ type Options struct {
Context context.Context Context context.Context
} }
func NewCallOptions(opts ...CallOption) CallOptions {
options := CallOptions{}
for _, o := range opts {
o(&options)
}
return options
}
type CallOptions struct { type CallOptions struct {
// Address of remote hosts // Address of remote hosts
Address []string Address []string
// Backoff func // Backoff func
Backoff BackoffFunc Backoff BackoffFunc
// Duration to cache the response for
CacheExpiry time.Duration
// Transport Dial Timeout // Transport Dial Timeout
DialTimeout time.Duration DialTimeout time.Duration
// Number of Call attempts // Number of Call attempts
@@ -84,6 +87,20 @@ type CallOptions struct {
Context context.Context Context context.Context
} }
func Context(ctx context.Context) Option {
return func(o *Options) {
o.Context = ctx
}
}
func NewPublishOptions(opts ...PublishOption) PublishOptions {
options := PublishOptions{}
for _, o := range opts {
o(&options)
}
return options
}
type PublishOptions struct { type PublishOptions struct {
// Exchange is the routing exchange for the message // Exchange is the routing exchange for the message
Exchange string Exchange string
@@ -92,10 +109,26 @@ type PublishOptions struct {
Context context.Context Context context.Context
} }
func NewMessageOptions(opts ...MessageOption) MessageOptions {
options := MessageOptions{}
for _, o := range opts {
o(&options)
}
return options
}
type MessageOptions struct { type MessageOptions struct {
ContentType string ContentType string
} }
func NewRequestOptions(opts ...RequestOption) RequestOptions {
options := RequestOptions{}
for _, o := range opts {
o(&options)
}
return options
}
type RequestOptions struct { type RequestOptions struct {
ContentType string ContentType string
Stream bool Stream bool
@@ -105,9 +138,8 @@ type RequestOptions struct {
Context context.Context Context context.Context
} }
func NewOptions(options ...Option) Options { func NewOptions(opts ...Option) Options {
opts := Options{ options := Options{
Cache: NewCache(),
Context: context.Background(), Context: context.Background(),
ContentType: "application/protobuf", ContentType: "application/protobuf",
Codecs: make(map[string]codec.NewCodec), Codecs: make(map[string]codec.NewCodec),
@@ -122,13 +154,15 @@ func NewOptions(options ...Option) Options {
PoolSize: DefaultPoolSize, PoolSize: DefaultPoolSize,
PoolTTL: DefaultPoolTTL, PoolTTL: DefaultPoolTTL,
Selector: random.NewSelector(), Selector: random.NewSelector(),
Logger: logger.DefaultLogger,
Broker: broker.DefaultBroker,
} }
for _, o := range options { for _, o := range opts {
o(&opts) o(&options)
} }
return opts return options
} }
// Broker to be used for pub/sub // Broker to be used for pub/sub
@@ -360,14 +394,6 @@ func WithAuthToken() CallOption {
} }
} }
// WithCache is a CallOption which sets the duration the response
// shoull be cached for
func WithCache(c time.Duration) CallOption {
return func(o *CallOptions) {
o.CacheExpiry = c
}
}
// WithNetwork is a CallOption which sets the network attribute // WithNetwork is a CallOption which sets the network attribute
func WithNetwork(n string) CallOption { func WithNetwork(n string) CallOption {
return func(o *CallOptions) { return func(o *CallOptions) {

View File

@@ -14,28 +14,6 @@ type testRequest struct {
opts RequestOptions opts RequestOptions
} }
func newRequest(service, endpoint string, request interface{}, contentType string, reqOpts ...RequestOption) Request {
var opts RequestOptions
for _, o := range reqOpts {
o(&opts)
}
// set the content-type specified
if len(opts.ContentType) > 0 {
contentType = opts.ContentType
}
return &testRequest{
service: service,
method: endpoint,
endpoint: endpoint,
body: request,
contentType: contentType,
opts: opts,
}
}
func (r *testRequest) ContentType() string { func (r *testRequest) ContentType() string {
return r.contentType return r.contentType
} }

View File

@@ -236,7 +236,7 @@ func (g *micro) generateService(file *generator.FileDescriptor, service *pb.Serv
outType := g.typeName(method.GetOutputType()) outType := g.typeName(method.GetOutputType())
if !method.GetServerStreaming() && !method.GetClientStreaming() { if !method.GetServerStreaming() && !method.GetClientStreaming() {
g.P(methName, "(ctx ", contextPkg, ".Context, in *", inType, ", out *", outType, ") error") g.P(methName, "(ctx ", contextPkg, ".Context, req *", inType, ", rsp *", outType, ") error")
continue continue
} }
g.P(methName, "(ctx ", contextPkg, ".Context, stream server.Stream) error") g.P(methName, "(ctx ", contextPkg, ".Context, stream server.Stream) error")
@@ -322,7 +322,7 @@ func (g *micro) generateClientSignature(servName string, method *pb.MethodDescri
if reservedClientName[methName] { if reservedClientName[methName] {
methName += "_" methName += "_"
} }
reqArg := ", in *" + g.typeName(method.GetInputType()) reqArg := ", req *" + g.typeName(method.GetInputType())
if method.GetClientStreaming() { if method.GetClientStreaming() {
reqArg = "" reqArg = ""
} }
@@ -335,8 +335,8 @@ func (g *micro) generateClientSignature(servName string, method *pb.MethodDescri
} }
func (g *micro) generateClientMethod(reqServ, servName, serviceDescVar string, method *pb.MethodDescriptorProto, descExpr string) { func (g *micro) generateClientMethod(reqServ, servName, serviceDescVar string, method *pb.MethodDescriptorProto, descExpr string) {
reqMethod := fmt.Sprintf("%s.%s", servName, method.GetName())
methName := generator.CamelCase(method.GetName()) methName := generator.CamelCase(method.GetName())
reqMethod := fmt.Sprintf("%s.%s", servName, methName)
inType := g.typeName(method.GetInputType()) inType := g.typeName(method.GetInputType())
outType := g.typeName(method.GetOutputType()) outType := g.typeName(method.GetOutputType())
@@ -349,23 +349,21 @@ func (g *micro) generateClientMethod(reqServ, servName, serviceDescVar string, m
g.P("func (c *", unexport(servAlias), ") ", g.generateClientSignature(servName, method), "{") g.P("func (c *", unexport(servAlias), ") ", g.generateClientSignature(servName, method), "{")
if !method.GetServerStreaming() && !method.GetClientStreaming() { if !method.GetServerStreaming() && !method.GetClientStreaming() {
g.P(`req := c.c.NewRequest(c.name, "`, reqMethod, `", in)`) g.P("rsp := &", outType, "{}")
g.P("out := new(", outType, ")")
// TODO: Pass descExpr to Invoke. // TODO: Pass descExpr to Invoke.
g.P("err := ", `c.c.Call(ctx, req, out, opts...)`) g.P(`err := c.c.Call(ctx, c.c.NewRequest(c.name, "`, reqMethod, `", req), rsp, opts...)`)
g.P("if err != nil { return nil, err }") g.P("if err != nil { return nil, err }")
g.P("return out, nil") g.P("return rsp, nil")
g.P("}") g.P("}")
g.P() g.P()
return return
} }
streamType := unexport(servAlias) + methName streamType := unexport(servAlias) + methName
g.P(`req := c.c.NewRequest(c.name, "`, reqMethod, `", &`, inType, `{})`) g.P(`stream, err := c.c.Stream(ctx, c.c.NewRequest(c.name, "`, reqMethod, `", &`, inType, `{}), opts...)`)
g.P("stream, err := c.c.Stream(ctx, req, opts...)")
g.P("if err != nil { return nil, err }") g.P("if err != nil { return nil, err }")
if !method.GetClientStreaming() { if !method.GetClientStreaming() {
g.P("if err := stream.Send(in); err != nil { return nil, err }") g.P("if err := stream.Send(req); err != nil { return nil, err }")
} }
g.P("return &", streamType, "{stream}, nil") g.P("return &", streamType, "{stream}, nil")
@@ -380,6 +378,11 @@ func (g *micro) generateClientMethod(reqServ, servName, serviceDescVar string, m
g.P("Context() context.Context") g.P("Context() context.Context")
g.P("SendMsg(interface{}) error") g.P("SendMsg(interface{}) error")
g.P("RecvMsg(interface{}) error") g.P("RecvMsg(interface{}) error")
if genSend && !genRecv {
// client streaming, the server will send a response upon close
g.P("CloseAndRecv() (*", outType, ", error)")
}
g.P("Close() error") g.P("Close() error")
if genSend { if genSend {
@@ -396,6 +399,18 @@ func (g *micro) generateClientMethod(reqServ, servName, serviceDescVar string, m
g.P("}") g.P("}")
g.P() g.P()
if genSend && !genRecv {
// client streaming, the server will send a response upon close
g.P("func (x *", streamType, ") CloseAndRecv() (*", outType, ", error) {")
g.P("if err := x.stream.Close(); err != nil {")
g.P("return nil, err")
g.P("}")
g.P("r := new(", outType, ")")
g.P("err := x.RecvMsg(r)")
g.P("return r, err")
g.P("}")
g.P()
}
g.P("func (x *", streamType, ") Close() error {") g.P("func (x *", streamType, ") Close() error {")
g.P("return x.stream.Close()") g.P("return x.stream.Close()")
g.P("}") g.P("}")
@@ -426,7 +441,7 @@ func (g *micro) generateClientMethod(reqServ, servName, serviceDescVar string, m
if genRecv { if genRecv {
g.P("func (x *", streamType, ") Recv() (*", outType, ", error) {") g.P("func (x *", streamType, ") Recv() (*", outType, ", error) {")
g.P("m := new(", outType, ")") g.P("m := &", outType, "{}")
g.P("err := x.stream.Recv(m)") g.P("err := x.stream.Recv(m)")
g.P("if err != nil {") g.P("if err != nil {")
g.P("return nil, err") g.P("return nil, err")
@@ -469,8 +484,8 @@ func (g *micro) generateServerMethod(servName string, method *pb.MethodDescripto
outType := g.typeName(method.GetOutputType()) outType := g.typeName(method.GetOutputType())
if !method.GetServerStreaming() && !method.GetClientStreaming() { if !method.GetServerStreaming() && !method.GetClientStreaming() {
g.P("func (h *", unexport(servName), "Handler) ", methName, "(ctx ", contextPkg, ".Context, in *", inType, ", out *", outType, ") error {") g.P("func (h *", unexport(servName), "Handler) ", methName, "(ctx ", contextPkg, ".Context, req *", inType, ", rsp *", outType, ") error {")
g.P("return h.", serveType, ".", methName, "(ctx, in, out)") g.P("return h.", serveType, ".", methName, "(ctx, req, rsp)")
g.P("}") g.P("}")
g.P() g.P()
return hname return hname
@@ -478,7 +493,7 @@ func (g *micro) generateServerMethod(servName string, method *pb.MethodDescripto
streamType := unexport(servName) + methName + "Stream" streamType := unexport(servName) + methName + "Stream"
g.P("func (h *", unexport(servName), "Handler) ", methName, "(ctx ", contextPkg, ".Context, stream server.Stream) error {") g.P("func (h *", unexport(servName), "Handler) ", methName, "(ctx ", contextPkg, ".Context, stream server.Stream) error {")
if !method.GetClientStreaming() { if !method.GetClientStreaming() {
g.P("m := new(", inType, ")") g.P("m := &", inType, "{}")
g.P("if err := stream.Recv(m); err != nil { return err }") g.P("if err := stream.Recv(m); err != nil { return err }")
g.P("return h.", serveType, ".", methName, "(ctx, m, &", streamType, "{stream})") g.P("return h.", serveType, ".", methName, "(ctx, m, &", streamType, "{stream})")
} else { } else {
@@ -495,6 +510,10 @@ func (g *micro) generateServerMethod(servName string, method *pb.MethodDescripto
g.P("Context() context.Context") g.P("Context() context.Context")
g.P("SendMsg(interface{}) error") g.P("SendMsg(interface{}) error")
g.P("RecvMsg(interface{}) error") g.P("RecvMsg(interface{}) error")
if !genSend {
// client streaming, the server will send a response upon close
g.P("SendAndClose(*", outType, ") error")
}
g.P("Close() error") g.P("Close() error")
if genSend { if genSend {
@@ -513,6 +532,17 @@ func (g *micro) generateServerMethod(servName string, method *pb.MethodDescripto
g.P("}") g.P("}")
g.P() g.P()
if !genSend {
// client streaming, the server will send a response upon close
g.P("func (x *", streamType, ") SendAndClose(in *", outType, ") error {")
g.P("if err := x.SendMsg(in); err != nil {")
g.P("return err")
g.P("}")
g.P("return x.stream.Close()")
g.P("}")
g.P()
}
// other types of rpc don't send a response when the stream closes
g.P("func (x *", streamType, ") Close() error {") g.P("func (x *", streamType, ") Close() error {")
g.P("return x.stream.Close()") g.P("return x.stream.Close()")
g.P("}") g.P("}")
@@ -542,7 +572,7 @@ func (g *micro) generateServerMethod(servName string, method *pb.MethodDescripto
if genRecv { if genRecv {
g.P("func (x *", streamType, ") Recv() (*", inType, ", error) {") g.P("func (x *", streamType, ") Recv() (*", inType, ", error) {")
g.P("m := new(", inType, ")") g.P("m := &", inType, "{}")
g.P("if err := x.stream.Recv(m); err != nil { return nil, err }") g.P("if err := x.stream.Recv(m); err != nil { return nil, err }")
g.P("return m, nil") g.P("return m, nil")
g.P("}") g.P("}")

View File

@@ -14,12 +14,14 @@ const (
) )
var ( var (
// ErrInvalidMessage returned when invalid messge passed to codec
ErrInvalidMessage = errors.New("invalid message") ErrInvalidMessage = errors.New("invalid message")
) )
// MessageType
type MessageType int type MessageType int
// Takes in a connection/buffer and returns a new Codec // NewCodec takes in a connection/buffer and returns a new Codec
type NewCodec func(io.ReadWriteCloser) Codec type NewCodec func(io.ReadWriteCloser) Codec
// Codec encodes/decodes various types of messages used within go-micro. // Codec encodes/decodes various types of messages used within go-micro.
@@ -34,11 +36,13 @@ type Codec interface {
String() string String() string
} }
// Reader interface
type Reader interface { type Reader interface {
ReadHeader(*Message, MessageType) error ReadHeader(*Message, MessageType) error
ReadBody(interface{}) error ReadBody(interface{}) error
} }
// Writer interface
type Writer interface { type Writer interface {
Write(*Message, interface{}) error Write(*Message, interface{}) error
} }

View File

@@ -1,59 +0,0 @@
// Package json provides a json codec
package json
import (
"encoding/json"
"io"
"io/ioutil"
"github.com/unistack-org/micro/v3/codec"
jsonpb "google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
)
type Codec struct {
Conn io.ReadWriteCloser
Encoder *json.Encoder
Decoder *json.Decoder
}
func (c *Codec) ReadHeader(m *codec.Message, t codec.MessageType) error {
return nil
}
func (c *Codec) ReadBody(b interface{}) error {
if b == nil {
return nil
}
if pb, ok := b.(proto.Message); ok {
buf, err := ioutil.ReadAll(c.Conn)
if err != nil {
return err
}
return jsonpb.Unmarshal(buf, pb)
}
return c.Decoder.Decode(b)
}
func (c *Codec) Write(m *codec.Message, b interface{}) error {
if b == nil {
return nil
}
return c.Encoder.Encode(b)
}
func (c *Codec) Close() error {
return c.Conn.Close()
}
func (c *Codec) String() string {
return "json"
}
func NewCodec(c io.ReadWriteCloser) codec.Codec {
return &Codec{
Conn: c,
Decoder: json.NewDecoder(c),
Encoder: json.NewEncoder(c),
}
}

View File

@@ -1,38 +0,0 @@
package json
import (
"encoding/json"
"github.com/oxtoacart/bpool"
jsonpb "google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
)
var jsonpbMarshaler = &jsonpb.MarshalOptions{}
// create buffer pool with 16 instances each preallocated with 256 bytes
var bufferPool = bpool.NewSizedBufferPool(16, 256)
type Marshaler struct{}
func (j Marshaler) Marshal(v interface{}) ([]byte, error) {
if pb, ok := v.(proto.Message); ok {
buf, err := jsonpbMarshaler.Marshal(pb)
if err != nil {
return nil, err
}
return buf, nil
}
return json.Marshal(v)
}
func (j Marshaler) Unmarshal(d []byte, v interface{}) error {
if pb, ok := v.(proto.Message); ok {
return jsonpb.Unmarshal(d, pb)
}
return json.Unmarshal(d, v)
}
func (j Marshaler) String() string {
return "json"
}

View File

@@ -1,97 +0,0 @@
package jsonrpc
import (
"encoding/json"
"fmt"
"io"
"sync"
"github.com/unistack-org/micro/v3/codec"
)
type clientCodec struct {
dec *json.Decoder // for reading JSON values
enc *json.Encoder // for writing JSON values
c io.Closer
// temporary work space
req clientRequest
resp clientResponse
sync.Mutex
pending map[interface{}]string
}
type clientRequest struct {
Method string `json:"method"`
Params [1]interface{} `json:"params"`
ID interface{} `json:"id"`
}
type clientResponse struct {
ID interface{} `json:"id"`
Result *json.RawMessage `json:"result"`
Error interface{} `json:"error"`
}
func newClientCodec(conn io.ReadWriteCloser) *clientCodec {
return &clientCodec{
dec: json.NewDecoder(conn),
enc: json.NewEncoder(conn),
c: conn,
pending: make(map[interface{}]string),
}
}
func (c *clientCodec) Write(m *codec.Message, b interface{}) error {
c.Lock()
c.pending[m.Id] = m.Method
c.Unlock()
c.req.Method = m.Method
c.req.Params[0] = b
c.req.ID = m.Id
return c.enc.Encode(&c.req)
}
func (r *clientResponse) reset() {
r.ID = 0
r.Result = nil
r.Error = nil
}
func (c *clientCodec) ReadHeader(m *codec.Message) error {
c.resp.reset()
if err := c.dec.Decode(&c.resp); err != nil {
return err
}
c.Lock()
m.Method = c.pending[c.resp.ID]
delete(c.pending, c.resp.ID)
c.Unlock()
m.Error = ""
m.Id = fmt.Sprintf("%v", c.resp.ID)
if c.resp.Error != nil {
x, ok := c.resp.Error.(string)
if !ok {
return fmt.Errorf("invalid error %v", c.resp.Error)
}
if x == "" {
x = "unspecified error"
}
m.Error = x
}
return nil
}
func (c *clientCodec) ReadBody(x interface{}) error {
if x == nil || c.resp.Result == nil {
return nil
}
return json.Unmarshal(*c.resp.Result, x)
}
func (c *clientCodec) Close() error {
return c.c.Close()
}

View File

@@ -1,88 +0,0 @@
// Package jsonrpc provides a json-rpc 1.0 codec
package jsonrpc
import (
"bytes"
"encoding/json"
"fmt"
"io"
"github.com/unistack-org/micro/v3/codec"
)
type jsonCodec struct {
buf *bytes.Buffer
mt codec.MessageType
rwc io.ReadWriteCloser
c *clientCodec
s *serverCodec
}
func (j *jsonCodec) Close() error {
j.buf.Reset()
return j.rwc.Close()
}
func (j *jsonCodec) String() string {
return "json-rpc"
}
func (j *jsonCodec) Write(m *codec.Message, b interface{}) error {
switch m.Type {
case codec.Request:
return j.c.Write(m, b)
case codec.Response, codec.Error:
return j.s.Write(m, b)
case codec.Event:
data, err := json.Marshal(b)
if err != nil {
return err
}
_, err = j.rwc.Write(data)
return err
default:
return fmt.Errorf("Unrecognised message type: %v", m.Type)
}
}
func (j *jsonCodec) ReadHeader(m *codec.Message, mt codec.MessageType) error {
j.buf.Reset()
j.mt = mt
switch mt {
case codec.Request:
return j.s.ReadHeader(m)
case codec.Response:
return j.c.ReadHeader(m)
case codec.Event:
_, err := io.Copy(j.buf, j.rwc)
return err
default:
return fmt.Errorf("Unrecognised message type: %v", mt)
}
}
func (j *jsonCodec) ReadBody(b interface{}) error {
switch j.mt {
case codec.Request:
return j.s.ReadBody(b)
case codec.Response:
return j.c.ReadBody(b)
case codec.Event:
if b != nil {
return json.Unmarshal(j.buf.Bytes(), b)
}
default:
return fmt.Errorf("Unrecognised message type: %v", j.mt)
}
return nil
}
func NewCodec(rwc io.ReadWriteCloser) codec.Codec {
return &jsonCodec{
buf: bytes.NewBuffer(nil),
rwc: rwc,
c: newClientCodec(rwc),
s: newServerCodec(rwc),
}
}

View File

@@ -1,84 +0,0 @@
package jsonrpc
import (
"encoding/json"
"fmt"
"io"
"github.com/unistack-org/micro/v3/codec"
)
type serverCodec struct {
dec *json.Decoder // for reading JSON values
enc *json.Encoder // for writing JSON values
c io.Closer
// temporary work space
req serverRequest
}
type serverRequest struct {
Method string `json:"method"`
Params *json.RawMessage `json:"params"`
ID interface{} `json:"id"`
}
type serverResponse struct {
ID interface{} `json:"id"`
Result interface{} `json:"result"`
Error interface{} `json:"error"`
}
func newServerCodec(conn io.ReadWriteCloser) *serverCodec {
return &serverCodec{
dec: json.NewDecoder(conn),
enc: json.NewEncoder(conn),
c: conn,
}
}
func (r *serverRequest) reset() {
r.Method = ""
if r.Params != nil {
*r.Params = (*r.Params)[0:0]
}
if r.ID != nil {
r.ID = nil
}
}
func (c *serverCodec) ReadHeader(m *codec.Message) error {
c.req.reset()
if err := c.dec.Decode(&c.req); err != nil {
return err
}
m.Method = c.req.Method
m.Id = fmt.Sprintf("%v", c.req.ID)
c.req.ID = nil
return nil
}
func (c *serverCodec) ReadBody(x interface{}) error {
if x == nil {
return nil
}
var params [1]interface{}
params[0] = x
return json.Unmarshal(*c.req.Params, &params)
}
func (c *serverCodec) Write(m *codec.Message, x interface{}) error {
var resp serverResponse
resp.ID = m.Id
resp.Result = x
if m.Error == "" {
resp.Error = nil
} else {
resp.Error = m.Error
}
return c.enc.Encode(resp)
}
func (c *serverCodec) Close() error {
return c.c.Close()
}

View File

@@ -1,47 +0,0 @@
package proto
import (
"bytes"
"github.com/golang/protobuf/proto"
"github.com/oxtoacart/bpool"
"github.com/unistack-org/micro/v3/codec"
)
// create buffer pool with 16 instances each preallocated with 256 bytes
var bufferPool = bpool.NewSizedBufferPool(16, 256)
type Marshaler struct{}
func (Marshaler) Marshal(v interface{}) ([]byte, error) {
pb, ok := v.(proto.Message)
if !ok {
return nil, codec.ErrInvalidMessage
}
// looks not good, but allows to reuse underlining bytes
buf := bufferPool.Get()
pbuf := proto.NewBuffer(buf.Bytes())
defer func() {
bufferPool.Put(bytes.NewBuffer(pbuf.Bytes()))
}()
if err := pbuf.Marshal(pb); err != nil {
return nil, err
}
return pbuf.Bytes(), nil
}
func (Marshaler) Unmarshal(data []byte, v interface{}) error {
pb, ok := v.(proto.Message)
if !ok {
return codec.ErrInvalidMessage
}
return proto.Unmarshal(data, pb)
}
func (Marshaler) String() string {
return "proto"
}

View File

@@ -1,37 +0,0 @@
package proto
type Message struct {
Data []byte
}
func (m *Message) MarshalJSON() ([]byte, error) {
return m.Data, nil
}
func (m *Message) UnmarshalJSON(data []byte) error {
m.Data = data
return nil
}
func (m *Message) ProtoMessage() {}
func (m *Message) Reset() {
*m = Message{}
}
func (m *Message) String() string {
return string(m.Data)
}
func (m *Message) Marshal() ([]byte, error) {
return m.Data, nil
}
func (m *Message) Unmarshal(data []byte) error {
m.Data = data
return nil
}
func NewMessage(data []byte) *Message {
return &Message{data}
}

View File

@@ -1,64 +0,0 @@
// Package proto provides a proto codec
package proto
import (
"io"
"io/ioutil"
"github.com/unistack-org/micro/v3/codec"
"google.golang.org/protobuf/proto"
)
type Codec struct {
Conn io.ReadWriteCloser
}
func (c *Codec) ReadHeader(m *codec.Message, t codec.MessageType) error {
return nil
}
func (c *Codec) ReadBody(b interface{}) error {
if b == nil {
return nil
}
buf, err := ioutil.ReadAll(c.Conn)
if err != nil {
return err
}
m, ok := b.(proto.Message)
if !ok {
return codec.ErrInvalidMessage
}
return proto.Unmarshal(buf, m)
}
func (c *Codec) Write(m *codec.Message, b interface{}) error {
if b == nil {
// Nothing to write
return nil
}
p, ok := b.(proto.Message)
if !ok {
return codec.ErrInvalidMessage
}
buf, err := proto.Marshal(p)
if err != nil {
return err
}
_, err = c.Conn.Write(buf)
return err
}
func (c *Codec) Close() error {
return c.Conn.Close()
}
func (c *Codec) String() string {
return "proto"
}
func NewCodec(c io.ReadWriteCloser) codec.Codec {
return &Codec{
Conn: c,
}
}

View File

@@ -1,238 +0,0 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.25.0
// protoc v3.6.1
// source: codec/protorpc/envelope.proto
package protorpc
import (
proto "github.com/golang/protobuf/proto"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
// This is a compile-time assertion that a sufficiently up-to-date version
// of the legacy proto package is being used.
const _ = proto.ProtoPackageIsVersion4
type Request struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
ServiceMethod string `protobuf:"bytes,1,opt,name=service_method,json=serviceMethod,proto3" json:"service_method,omitempty"`
Seq uint64 `protobuf:"fixed64,2,opt,name=seq,proto3" json:"seq,omitempty"`
}
func (x *Request) Reset() {
*x = Request{}
if protoimpl.UnsafeEnabled {
mi := &file_codec_protorpc_envelope_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Request) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Request) ProtoMessage() {}
func (x *Request) ProtoReflect() protoreflect.Message {
mi := &file_codec_protorpc_envelope_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Request.ProtoReflect.Descriptor instead.
func (*Request) Descriptor() ([]byte, []int) {
return file_codec_protorpc_envelope_proto_rawDescGZIP(), []int{0}
}
func (x *Request) GetServiceMethod() string {
if x != nil {
return x.ServiceMethod
}
return ""
}
func (x *Request) GetSeq() uint64 {
if x != nil {
return x.Seq
}
return 0
}
type Response struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
ServiceMethod string `protobuf:"bytes,1,opt,name=service_method,json=serviceMethod,proto3" json:"service_method,omitempty"`
Seq uint64 `protobuf:"fixed64,2,opt,name=seq,proto3" json:"seq,omitempty"`
Error string `protobuf:"bytes,3,opt,name=error,proto3" json:"error,omitempty"`
}
func (x *Response) Reset() {
*x = Response{}
if protoimpl.UnsafeEnabled {
mi := &file_codec_protorpc_envelope_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Response) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Response) ProtoMessage() {}
func (x *Response) ProtoReflect() protoreflect.Message {
mi := &file_codec_protorpc_envelope_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Response.ProtoReflect.Descriptor instead.
func (*Response) Descriptor() ([]byte, []int) {
return file_codec_protorpc_envelope_proto_rawDescGZIP(), []int{1}
}
func (x *Response) GetServiceMethod() string {
if x != nil {
return x.ServiceMethod
}
return ""
}
func (x *Response) GetSeq() uint64 {
if x != nil {
return x.Seq
}
return 0
}
func (x *Response) GetError() string {
if x != nil {
return x.Error
}
return ""
}
var File_codec_protorpc_envelope_proto protoreflect.FileDescriptor
var file_codec_protorpc_envelope_proto_rawDesc = []byte{
0x0a, 0x1d, 0x63, 0x6f, 0x64, 0x65, 0x63, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x72, 0x70, 0x63,
0x2f, 0x65, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12,
0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x72, 0x70, 0x63, 0x22, 0x42, 0x0a, 0x07, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f,
0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x73, 0x65,
0x72, 0x76, 0x69, 0x63, 0x65, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x73,
0x65, 0x71, 0x18, 0x02, 0x20, 0x01, 0x28, 0x06, 0x52, 0x03, 0x73, 0x65, 0x71, 0x22, 0x59, 0x0a,
0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x65, 0x72,
0x76, 0x69, 0x63, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
0x09, 0x52, 0x0d, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64,
0x12, 0x10, 0x0a, 0x03, 0x73, 0x65, 0x71, 0x18, 0x02, 0x20, 0x01, 0x28, 0x06, 0x52, 0x03, 0x73,
0x65, 0x71, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28,
0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_codec_protorpc_envelope_proto_rawDescOnce sync.Once
file_codec_protorpc_envelope_proto_rawDescData = file_codec_protorpc_envelope_proto_rawDesc
)
func file_codec_protorpc_envelope_proto_rawDescGZIP() []byte {
file_codec_protorpc_envelope_proto_rawDescOnce.Do(func() {
file_codec_protorpc_envelope_proto_rawDescData = protoimpl.X.CompressGZIP(file_codec_protorpc_envelope_proto_rawDescData)
})
return file_codec_protorpc_envelope_proto_rawDescData
}
var file_codec_protorpc_envelope_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_codec_protorpc_envelope_proto_goTypes = []interface{}{
(*Request)(nil), // 0: protorpc.Request
(*Response)(nil), // 1: protorpc.Response
}
var file_codec_protorpc_envelope_proto_depIdxs = []int32{
0, // [0:0] is the sub-list for method output_type
0, // [0:0] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_codec_protorpc_envelope_proto_init() }
func file_codec_protorpc_envelope_proto_init() {
if File_codec_protorpc_envelope_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_codec_protorpc_envelope_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Request); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_codec_protorpc_envelope_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Response); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_codec_protorpc_envelope_proto_rawDesc,
NumEnums: 0,
NumMessages: 2,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_codec_protorpc_envelope_proto_goTypes,
DependencyIndexes: file_codec_protorpc_envelope_proto_depIdxs,
MessageInfos: file_codec_protorpc_envelope_proto_msgTypes,
}.Build()
File_codec_protorpc_envelope_proto = out.File
file_codec_protorpc_envelope_proto_rawDesc = nil
file_codec_protorpc_envelope_proto_goTypes = nil
file_codec_protorpc_envelope_proto_depIdxs = nil
}

View File

@@ -1,21 +0,0 @@
// Code generated by protoc-gen-micro. DO NOT EDIT.
// source: codec/protorpc/envelope.proto
package protorpc
import (
fmt "fmt"
proto "github.com/golang/protobuf/proto"
math "math"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package

View File

@@ -1,14 +0,0 @@
syntax = "proto3";
package protorpc;
message Request {
string service_method = 1;
fixed64 seq = 2;
}
message Response {
string service_method = 1;
fixed64 seq = 2;
string error = 3;
}

View File

@@ -1,36 +0,0 @@
package protorpc
import (
"encoding/binary"
"io"
)
// WriteNetString writes data to a big-endian netstring on a Writer.
// Size is always a 32-bit unsigned int.
func WriteNetString(w io.Writer, data []byte) (written int, err error) {
size := make([]byte, 4)
binary.BigEndian.PutUint32(size, uint32(len(data)))
if written, err = w.Write(size); err != nil {
return
}
return w.Write(data)
}
// ReadNetString reads data from a big-endian netstring.
func ReadNetString(r io.Reader) (data []byte, err error) {
sizeBuf := make([]byte, 4)
_, err = r.Read(sizeBuf)
if err != nil {
return nil, err
}
size := binary.BigEndian.Uint32(sizeBuf)
if size == 0 {
return nil, nil
}
data = make([]byte, size)
_, err = r.Read(data)
if err != nil {
return nil, err
}
return
}

View File

@@ -1,186 +0,0 @@
// Protorpc provides a net/rpc proto-rpc codec. See envelope.proto for the format.
package protorpc
import (
"bytes"
"fmt"
"io"
"strconv"
"sync"
"github.com/golang/protobuf/proto"
"github.com/unistack-org/micro/v3/codec"
)
type flusher interface {
Flush() error
}
type protoCodec struct {
sync.Mutex
rwc io.ReadWriteCloser
mt codec.MessageType
buf *bytes.Buffer
}
func (c *protoCodec) Close() error {
c.buf.Reset()
return c.rwc.Close()
}
func (c *protoCodec) String() string {
return "proto-rpc"
}
func id(id string) uint64 {
p, err := strconv.ParseInt(id, 10, 64)
if err != nil {
p = 0
}
i := uint64(p)
return i
}
func (c *protoCodec) Write(m *codec.Message, b interface{}) error {
switch m.Type {
case codec.Request:
c.Lock()
defer c.Unlock()
// This is protobuf, of course we copy it.
pbr := &Request{ServiceMethod: m.Method, Seq: id(m.Id)}
data, err := proto.Marshal(pbr)
if err != nil {
return err
}
_, err = WriteNetString(c.rwc, data)
if err != nil {
return err
}
// dont trust or incoming message
m, ok := b.(proto.Message)
if !ok {
return codec.ErrInvalidMessage
}
data, err = proto.Marshal(m)
if err != nil {
return err
}
_, err = WriteNetString(c.rwc, data)
if err != nil {
return err
}
if flusher, ok := c.rwc.(flusher); ok {
if err = flusher.Flush(); err != nil {
return err
}
}
case codec.Response, codec.Error:
c.Lock()
defer c.Unlock()
rtmp := &Response{ServiceMethod: m.Method, Seq: id(m.Id), Error: m.Error}
data, err := proto.Marshal(rtmp)
if err != nil {
return err
}
_, err = WriteNetString(c.rwc, data)
if err != nil {
return err
}
if pb, ok := b.(proto.Message); ok {
data, err = proto.Marshal(pb)
if err != nil {
return err
}
} else {
data = nil
}
_, err = WriteNetString(c.rwc, data)
if err != nil {
return err
}
if flusher, ok := c.rwc.(flusher); ok {
if err = flusher.Flush(); err != nil {
return err
}
}
case codec.Event:
m, ok := b.(proto.Message)
if !ok {
return codec.ErrInvalidMessage
}
data, err := proto.Marshal(m)
if err != nil {
return err
}
c.rwc.Write(data)
default:
return fmt.Errorf("Unrecognised message type: %v", m.Type)
}
return nil
}
func (c *protoCodec) ReadHeader(m *codec.Message, mt codec.MessageType) error {
c.buf.Reset()
c.mt = mt
switch mt {
case codec.Request:
data, err := ReadNetString(c.rwc)
if err != nil {
return err
}
rtmp := new(Request)
err = proto.Unmarshal(data, rtmp)
if err != nil {
return err
}
m.Method = rtmp.GetServiceMethod()
m.Id = fmt.Sprintf("%d", rtmp.GetSeq())
case codec.Response:
data, err := ReadNetString(c.rwc)
if err != nil {
return err
}
rtmp := new(Response)
err = proto.Unmarshal(data, rtmp)
if err != nil {
return err
}
m.Method = rtmp.GetServiceMethod()
m.Id = fmt.Sprintf("%d", rtmp.GetSeq())
m.Error = rtmp.GetError()
case codec.Event:
_, err := io.Copy(c.buf, c.rwc)
return err
default:
return fmt.Errorf("Unrecognised message type: %v", mt)
}
return nil
}
func (c *protoCodec) ReadBody(b interface{}) error {
var data []byte
switch c.mt {
case codec.Request, codec.Response:
var err error
data, err = ReadNetString(c.rwc)
if err != nil {
return err
}
case codec.Event:
data = c.buf.Bytes()
default:
return fmt.Errorf("Unrecognised message type: %v", c.mt)
}
if b != nil {
return proto.Unmarshal(data, b.(proto.Message))
}
return nil
}
func NewCodec(rwc io.ReadWriteCloser) codec.Codec {
return &protoCodec{
buf: bytes.NewBuffer(nil),
rwc: rwc,
}
}

View File

@@ -1,80 +0,0 @@
// Package text reads any text/* content-type
package text
import (
"fmt"
"io"
"io/ioutil"
"github.com/unistack-org/micro/v3/codec"
)
type Codec struct {
Conn io.ReadWriteCloser
}
// Frame gives us the ability to define raw data to send over the pipes
type Frame struct {
Data []byte
}
func (c *Codec) ReadHeader(m *codec.Message, t codec.MessageType) error {
return nil
}
func (c *Codec) ReadBody(b interface{}) error {
// read bytes
buf, err := ioutil.ReadAll(c.Conn)
if err != nil {
return err
}
switch v := b.(type) {
case *string:
*v = string(buf)
case *[]byte:
*v = buf
case *Frame:
v.Data = buf
default:
return fmt.Errorf("failed to read body: %v is not type of *[]byte", b)
}
return nil
}
func (c *Codec) Write(m *codec.Message, b interface{}) error {
var v []byte
switch ve := b.(type) {
case nil:
return nil
case *Frame:
v = ve.Data
case *[]byte:
v = *ve
case *string:
v = []byte(*ve)
case string:
v = []byte(ve)
case []byte:
v = ve
default:
return fmt.Errorf("failed to write: %v is not type of *[]byte or []byte", b)
}
_, err := c.Conn.Write(v)
return err
}
func (c *Codec) Close() error {
return c.Conn.Close()
}
func (c *Codec) String() string {
return "text"
}
func NewCodec(c io.ReadWriteCloser) codec.Codec {
return &Codec{
Conn: c,
}
}

View File

@@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"github.com/unistack-org/micro/v3/debug/log" "github.com/unistack-org/micro/v3/debug/log"
"github.com/unistack-org/micro/v3/metadata"
"github.com/unistack-org/micro/v3/util/ring" "github.com/unistack-org/micro/v3/util/ring"
) )
@@ -94,7 +95,7 @@ func (l *memoryLog) Stream() (log.Stream, error) {
records <- log.Record{ records <- log.Record{
Timestamp: entry.Timestamp, Timestamp: entry.Timestamp,
Message: entry.Value, Message: entry.Value,
Metadata: make(map[string]string), Metadata: metadata.New(0),
} }
} }
// now stream continuously // now stream continuously
@@ -102,7 +103,7 @@ func (l *memoryLog) Stream() (log.Stream, error) {
records <- log.Record{ records <- log.Record{
Timestamp: entry.Timestamp, Timestamp: entry.Timestamp,
Message: entry.Value, Message: entry.Value,
Metadata: make(map[string]string), Metadata: metadata.New(0),
} }
} }
}() }()

View File

@@ -11,20 +11,20 @@ type Profile interface {
} }
var ( var (
DefaultProfile Profile = new(noop) DefaultProfile Profile = &NoopProfile{}
) )
type noop struct{} type NoopProfile struct{}
func (p *noop) Start() error { func (p *NoopProfile) Start() error {
return nil return nil
} }
func (p *noop) Stop() error { func (p *NoopProfile) Stop() error {
return nil return nil
} }
func (p *noop) String() string { func (p *NoopProfile) String() string {
return "noop" return "noop"
} }

View File

@@ -1,90 +0,0 @@
package memory
import (
"context"
"time"
"github.com/google/uuid"
"github.com/unistack-org/micro/v3/debug/trace"
"github.com/unistack-org/micro/v3/util/ring"
)
type Tracer struct {
opts trace.Options
// ring buffer of traces
buffer *ring.Buffer
}
func (t *Tracer) Read(opts ...trace.ReadOption) ([]*trace.Span, error) {
var options trace.ReadOptions
for _, o := range opts {
o(&options)
}
sp := t.buffer.Get(t.buffer.Size())
spans := make([]*trace.Span, 0, len(sp))
for _, span := range sp {
val := span.Value.(*trace.Span)
// skip if trace id is specified and doesn't match
if len(options.Trace) > 0 && val.Trace != options.Trace {
continue
}
spans = append(spans, val)
}
return spans, nil
}
func (t *Tracer) Start(ctx context.Context, name string) (context.Context, *trace.Span) {
span := &trace.Span{
Name: name,
Trace: uuid.New().String(),
Id: uuid.New().String(),
Started: time.Now(),
Metadata: make(map[string]string),
}
// return span if no context
if ctx == nil {
return trace.ToContext(context.Background(), span.Trace, span.Id), span
}
traceID, parentSpanID, ok := trace.FromContext(ctx)
// If the trace can not be found in the header,
// that means this is where the trace is created.
if !ok {
return trace.ToContext(ctx, span.Trace, span.Id), span
}
// set trace id
span.Trace = traceID
// set parent
span.Parent = parentSpanID
// return the span
return trace.ToContext(ctx, span.Trace, span.Id), span
}
func (t *Tracer) Finish(s *trace.Span) error {
// set finished time
s.Duration = time.Since(s.Started)
// save the span
t.buffer.Put(s)
return nil
}
func NewTracer(opts ...trace.Option) trace.Tracer {
var options trace.Options
for _, o := range opts {
o(&options)
}
return &Tracer{
opts: options,
// the last 256 requests
buffer: ring.New(256),
}
}

View File

@@ -1,99 +0,0 @@
// Package trace provides an interface for distributed tracing
package trace
import (
"context"
"time"
"github.com/unistack-org/micro/v3/metadata"
)
// Tracer is an interface for distributed tracing
type Tracer interface {
// Start a trace
Start(ctx context.Context, name string) (context.Context, *Span)
// Finish the trace
Finish(*Span) error
// Read the traces
Read(...ReadOption) ([]*Span, error)
}
// SpanType describe the nature of the trace span
type SpanType int
const (
// SpanTypeRequestInbound is a span created when serving a request
SpanTypeRequestInbound SpanType = iota
// SpanTypeRequestOutbound is a span created when making a service call
SpanTypeRequestOutbound
)
// Span is used to record an entry
type Span struct {
// Id of the trace
Trace string
// name of the span
Name string
// id of the span
Id string
// parent span id
Parent string
// Start time
Started time.Time
// Duration in nano seconds
Duration time.Duration
// associated data
Metadata map[string]string
// Type
Type SpanType
}
const (
traceIDKey = "Micro-Trace-Id"
spanIDKey = "Micro-Span-Id"
)
// FromContext returns a span from context
func FromContext(ctx context.Context) (traceID string, parentSpanID string, isFound bool) {
traceID, traceOk := metadata.Get(ctx, traceIDKey)
microID, microOk := metadata.Get(ctx, "Micro-Id")
if !traceOk && !microOk {
isFound = false
return
}
if !traceOk {
traceID = microID
}
parentSpanID, ok := metadata.Get(ctx, spanIDKey)
return traceID, parentSpanID, ok
}
// ToContext saves the trace and span ids in the context
func ToContext(ctx context.Context, traceID, parentSpanID string) context.Context {
return metadata.MergeContext(ctx, map[string]string{
traceIDKey: traceID,
spanIDKey: parentSpanID,
}, true)
}
var (
DefaultTracer Tracer = new(noop)
)
type noop struct{}
func (n *noop) Init(...Option) error {
return nil
}
func (n *noop) Start(ctx context.Context, name string) (context.Context, *Span) {
return nil, nil
}
func (n *noop) Finish(*Span) error {
return nil
}
func (n *noop) Read(...ReadOption) ([]*Span, error) {
return nil, nil
}

View File

@@ -8,6 +8,22 @@ import (
"net/http" "net/http"
) )
var (
ErrBadRequest = &Error{Code: 400}
ErrUnauthorized = &Error{Code: 401}
ErrForbidden = &Error{Code: 403}
ErrNotFound = &Error{Code: 404}
ErrMethodNotAllowed = &Error{Code: 405}
ErrTimeout = &Error{Code: 408}
ErrConflict = &Error{Code: 409}
ErrInternalServerError = &Error{Code: 500}
ErNotImplemented = &Error{Code: 501}
ErrBadGateway = &Error{Code: 502}
ErrServiceUnavailable = &Error{Code: 503}
ErrGatewayTimeout = &Error{Code: 504}
)
// Error tpye
type Error struct { type Error struct {
Id string Id string
Code int32 Code int32

View File

@@ -2,6 +2,7 @@
package events package events
import ( import (
"context"
"encoding/json" "encoding/json"
"errors" "errors"
"time" "time"
@@ -16,14 +17,14 @@ var (
// Stream of events // Stream of events
type Stream interface { type Stream interface {
Publish(topic string, msg interface{}, opts ...PublishOption) error Publish(ctx context.Context, topic string, msg interface{}, opts ...PublishOption) error
Subscribe(topic string, opts ...SubscribeOption) (<-chan Event, error) Subscribe(ctx context.Context, topic string, opts ...SubscribeOption) (<-chan Event, error)
} }
// Store of events // Store of events
type Store interface { type Store interface {
Read(opts ...ReadOption) ([]*Event, error) Read(ctx context.Context, opts ...ReadOption) ([]*Event, error)
Write(event *Event, opts ...WriteOption) error Write(ctx context.Context, event *Event, opts ...WriteOption) error
} }
// Event is the object returned by the broker when you subscribe to a topic // Event is the object returned by the broker when you subscribe to a topic

View File

@@ -8,14 +8,13 @@ import (
"testing" "testing"
rmemory "github.com/unistack-org/micro-registry-memory" rmemory "github.com/unistack-org/micro-registry-memory"
"github.com/unistack-org/micro/v3/util/test"
) )
func TestFunction(t *testing.T) { func TestFunction(t *testing.T) {
var wg sync.WaitGroup var wg sync.WaitGroup
wg.Add(1) wg.Add(1)
r := rmemory.NewRegistry(rmemory.Services(test.Data)) r := rmemory.NewRegistry()
// create service // create service
fn := NewFunction( fn := NewFunction(

28
go.mod
View File

@@ -1,33 +1,33 @@
module github.com/unistack-org/micro/v3 module github.com/unistack-org/micro/v3
go 1.15 go 1.14
require ( require (
github.com/BurntSushi/toml v0.3.1 github.com/BurntSushi/toml v0.3.1
github.com/caddyserver/certmagic v0.10.6 github.com/caddyserver/certmagic v0.10.6
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/ef-ds/deque v1.0.4-0.20190904040645-54cb57c252a1 github.com/ef-ds/deque v1.0.4-0.20190904040645-54cb57c252a1
github.com/evanphx/json-patch/v5 v5.0.0 github.com/evanphx/json-patch/v5 v5.1.0
github.com/ghodss/yaml v1.0.0 github.com/ghodss/yaml v1.0.0
github.com/go-acme/lego/v3 v3.4.0 github.com/go-acme/lego/v3 v3.4.0
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee
github.com/gobwas/ws v1.0.3 github.com/gobwas/ws v1.0.3
github.com/golang/protobuf v1.4.2 github.com/golang/protobuf v1.4.3
github.com/google/uuid v1.1.1 github.com/google/uuid v1.1.2
github.com/hashicorp/hcl v1.0.0 github.com/hashicorp/hcl v1.0.0
github.com/kr/text v0.2.0 // indirect
github.com/micro/cli/v2 v2.1.2 github.com/micro/cli/v2 v2.1.2
github.com/miekg/dns v1.1.27 github.com/miekg/dns v1.1.31
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c
github.com/patrickmn/go-cache v2.1.0+incompatible github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/stretchr/testify v1.5.1 github.com/stretchr/testify v1.5.1
github.com/unistack-org/micro-codec-bytes v0.0.0-20200827104921-3616a69473a6 github.com/unistack-org/micro-codec-bytes v0.0.0-20200828083432-4e49e953d844
github.com/unistack-org/micro-config-cmd v0.0.0-20200828075439-d859b9d7265b github.com/unistack-org/micro-codec-json v0.0.0-20201102222734-a29c895ec05c
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899 github.com/unistack-org/micro-codec-jsonrpc v0.0.0-20201102222451-ff6a69988bcd
golang.org/x/net v0.0.0-20200707034311-ab3426394381 github.com/unistack-org/micro-codec-proto v0.0.0-20201102222202-769c2d6a4b92
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 github.com/unistack-org/micro-codec-protorpc v0.0.0-20201102222610-3a343898c077
github.com/unistack-org/micro-config-cmd v0.0.0-20201028144621-5a55f1aad70a
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a
golang.org/x/net v0.0.0-20200904194848-62affa334b73
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d
google.golang.org/protobuf v1.25.0 google.golang.org/protobuf v1.25.0
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
) )

40
go.sum
View File

@@ -75,6 +75,8 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/evanphx/json-patch/v5 v5.0.0 h1:dKTrUeykyQwKb/kx7Z+4ukDs6l+4L41HqG1XHnhX7WE= github.com/evanphx/json-patch/v5 v5.0.0 h1:dKTrUeykyQwKb/kx7Z+4ukDs6l+4L41HqG1XHnhX7WE=
github.com/evanphx/json-patch/v5 v5.0.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= github.com/evanphx/json-patch/v5 v5.0.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4=
github.com/evanphx/json-patch/v5 v5.1.0 h1:B0aXl1o/1cP8NbviYiBMkcHBtUjIJ1/Ccg6b+SwCLQg=
github.com/evanphx/json-patch/v5 v5.1.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4=
github.com/exoscale/egoscale v0.18.1/go.mod h1:Z7OOdzzTOz1Q1PjQXumlz9Wn/CddH0zSYdCF3rnBKXE= github.com/exoscale/egoscale v0.18.1/go.mod h1:Z7OOdzzTOz1Q1PjQXumlz9Wn/CddH0zSYdCF3rnBKXE=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
@@ -119,6 +121,8 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
@@ -139,6 +143,8 @@ github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OI
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gophercloud/gophercloud v0.3.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= github.com/gophercloud/gophercloud v0.3.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
@@ -197,6 +203,8 @@ github.com/micro/cli/v2 v2.1.2/go.mod h1:EguNh6DAoWKm9nmk+k/Rg0H3lQnDxqzu5x5srOt
github.com/miekg/dns v1.1.15/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.15/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.27 h1:aEH/kqUzUxGJ/UHcEKdJY+ugH6WEzsEBBSPa8zuy1aM= github.com/miekg/dns v1.1.27 h1:aEH/kqUzUxGJ/UHcEKdJY+ugH6WEzsEBBSPa8zuy1aM=
github.com/miekg/dns v1.1.27/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/miekg/dns v1.1.27/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
github.com/miekg/dns v1.1.31 h1:sJFOl9BgwbYAWOGEwr61FU28pqsBNdpRBnhGXtO06Oo=
github.com/miekg/dns v1.1.31/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-vnc v0.0.0-20150629162542-723ed9867aed/go.mod h1:3rdaFaCv4AyBgu5ALFM0+tSuHrBh6v692nyQe3ikrq0= github.com/mitchellh/go-vnc v0.0.0-20150629162542-723ed9867aed/go.mod h1:3rdaFaCv4AyBgu5ALFM0+tSuHrBh6v692nyQe3ikrq0=
@@ -275,11 +283,31 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
github.com/timewasted/linode v0.0.0-20160829202747-37e84520dcf7/go.mod h1:imsgLplxEC/etjIhdr3dNzV3JeT27LbVu5pYWm0JCBY= github.com/timewasted/linode v0.0.0-20160829202747-37e84520dcf7/go.mod h1:imsgLplxEC/etjIhdr3dNzV3JeT27LbVu5pYWm0JCBY=
github.com/transip/gotransip v0.0.0-20190812104329-6d8d9179b66f/go.mod h1:i0f4R4o2HM0m3DZYQWsj6/MEowD57VzoH0v3d7igeFY= github.com/transip/gotransip v0.0.0-20190812104329-6d8d9179b66f/go.mod h1:i0f4R4o2HM0m3DZYQWsj6/MEowD57VzoH0v3d7igeFY=
github.com/uber-go/atomic v1.3.2/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g= github.com/uber-go/atomic v1.3.2/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g=
github.com/unistack-org/micro-codec-bytes v0.0.0-20200827104921-3616a69473a6 h1:bBPX47ly/xhLhuCMIvybXKevCOCZCmxzvDwR61zu3cQ=
github.com/unistack-org/micro-codec-bytes v0.0.0-20200827104921-3616a69473a6/go.mod h1:g5sOI8TWgGZiVHe8zoUPdtz7+0oLnqTnfBoai6Qb7jE= github.com/unistack-org/micro-codec-bytes v0.0.0-20200827104921-3616a69473a6/go.mod h1:g5sOI8TWgGZiVHe8zoUPdtz7+0oLnqTnfBoai6Qb7jE=
github.com/unistack-org/micro-config-cmd v0.0.0-20200828075439-d859b9d7265b h1:v5Ak+Sr780jZclFDnx82g5biF0N5HRVKphEpJhbnVUs= github.com/unistack-org/micro-codec-bytes v0.0.0-20200828083432-4e49e953d844 h1:5b1yuSllbsMm/9fUIlIXSr8DbsKT/sAKSCgOx6+SAfI=
github.com/unistack-org/micro-codec-bytes v0.0.0-20200828083432-4e49e953d844/go.mod h1:g5sOI8TWgGZiVHe8zoUPdtz7+0oLnqTnfBoai6Qb7jE=
github.com/unistack-org/micro-codec-json v0.0.0-20201102222734-a29c895ec05c h1:RtcNaK8rQSl7xAoy1W437dvZLCVjSC6e4JcolepSQs0=
github.com/unistack-org/micro-codec-json v0.0.0-20201102222734-a29c895ec05c/go.mod h1:dG5aUyhBv+ebOl/UFW2Aj2GTfVxxXWi6AcynpePOAhQ=
github.com/unistack-org/micro-codec-jsonrpc v0.0.0-20201102222451-ff6a69988bcd h1:qXSiEfVnCgrwTHYvAnEPSHEai3+5EUH9ZYovLpxGDwg=
github.com/unistack-org/micro-codec-jsonrpc v0.0.0-20201102222451-ff6a69988bcd/go.mod h1:PFyvkGhavl+3tEPgOaLAhoJJX4/webVGW59BSOXDfNM=
github.com/unistack-org/micro-codec-proto v0.0.0-20201102222202-769c2d6a4b92 h1:1rPDBu7Nwo3ZL6r6H5rj7qNchHSdBF4zcewAeTUEMC4=
github.com/unistack-org/micro-codec-proto v0.0.0-20201102222202-769c2d6a4b92/go.mod h1:31JMo683bBQ+uN9YufpUU6ESHphyx3DFmTXEnjpJV9Y=
github.com/unistack-org/micro-codec-protorpc v0.0.0-20201102222610-3a343898c077 h1:uK7owL8TPSwoQiDM1V/0swmgCEepSQKXoi8GEnGxtlU=
github.com/unistack-org/micro-codec-protorpc v0.0.0-20201102222610-3a343898c077/go.mod h1:Ct4uAVZaDEyBZj9Q0poDkbzu6zKXUCcSqJkv/MWPpeI=
github.com/unistack-org/micro-config-cmd v0.0.0-20200828075439-d859b9d7265b/go.mod h1:6pm1cadbwsFcEW1ZbV5Fp0i3goR3TNfROMNSPih3I8k= github.com/unistack-org/micro-config-cmd v0.0.0-20200828075439-d859b9d7265b/go.mod h1:6pm1cadbwsFcEW1ZbV5Fp0i3goR3TNfROMNSPih3I8k=
github.com/unistack-org/micro-config-cmd v0.0.0-20200909210346-ec89783dc46c h1:GbcjxyOyA9tnNoe4FcnzzLDa8JwEBnQKN/7Bhd8t47I=
github.com/unistack-org/micro-config-cmd v0.0.0-20200909210346-ec89783dc46c/go.mod h1:6pm1cadbwsFcEW1ZbV5Fp0i3goR3TNfROMNSPih3I8k=
github.com/unistack-org/micro-config-cmd v0.0.0-20200909210755-6e7e85eeab34 h1:VHc98t4SoiCF/jbkFu2e/j+IyJ/+MFQ1T+INNL7LubU=
github.com/unistack-org/micro-config-cmd v0.0.0-20200909210755-6e7e85eeab34/go.mod h1:fT1gYn+TtfVZZ5tNx56bZIncJjmlji66g7GKdWua5hE=
github.com/unistack-org/micro-config-cmd v0.0.0-20200920140133-0853deb2e5dc h1:hHAU3rgeiA0LaudfNdMLf9/jkOBeFxvJdnwXevviZF8=
github.com/unistack-org/micro-config-cmd v0.0.0-20200920140133-0853deb2e5dc/go.mod h1:il8nz4ZEcX3Usyfrtwy+YtQcb7xSUSFJdSe8PBJ9gOA=
github.com/unistack-org/micro-config-cmd v0.0.0-20201028144621-5a55f1aad70a h1:VjlqP1qZkjC0Chmx5MKFPIbtSCigeICFDf8vaLZGh9o=
github.com/unistack-org/micro-config-cmd v0.0.0-20201028144621-5a55f1aad70a/go.mod h1:MzMg+qh1wORZwYtg5AVgFkNFrXVVbdPKW7s/Is+A994=
github.com/unistack-org/micro/v3 v3.0.0-20200827083227-aa99378adc6e/go.mod h1:rPQbnry3nboAnMczj8B1Gzlcyv/HYoMZLgd3/3nttJ4= github.com/unistack-org/micro/v3 v3.0.0-20200827083227-aa99378adc6e/go.mod h1:rPQbnry3nboAnMczj8B1Gzlcyv/HYoMZLgd3/3nttJ4=
github.com/unistack-org/micro/v3 v3.0.0-gamma/go.mod h1:iEtpu3wTYCRs3pQ3VsFEO7JBO4lOMpkOwMyrpZyIDPo=
github.com/unistack-org/micro/v3 v3.0.0-gamma.0.20200909210629-caec730248b1/go.mod h1:mmqHR9WelHUXqg2mELjsQ+FJHcWs6mNmXg+wEYO2T3c=
github.com/unistack-org/micro/v3 v3.0.0-gamma.0.20200920135754-1cbd1d2bad83/go.mod h1:HUzMG4Mcy97958VxWTg8zuazZgwQ/aoLZ8wtBVONwRE=
github.com/unistack-org/micro/v3 v3.0.0-gamma.0.20200922103357-4c4fa00a5d94/go.mod h1:aL+8VhSXpx0SuEeXPOWUo5BgS7kyvWYobeXFay90UUM=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/vultr/govultr v0.1.4/go.mod h1:9H008Uxr/C4vFNGLqKx232C206GL0PBHzOP0809bGNA= github.com/vultr/govultr v0.1.4/go.mod h1:9H008Uxr/C4vFNGLqKx232C206GL0PBHzOP0809bGNA=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
@@ -303,6 +331,8 @@ golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899 h1:DZhuSZLsGlFL4CmhA8BcRA0mnthyA/nZ00AqCUo7vHg= golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899 h1:DZhuSZLsGlFL4CmhA8BcRA0mnthyA/nZ00AqCUo7vHg=
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM=
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -345,6 +375,8 @@ golang.org/x/net v0.0.0-20190930134127-c5a3c61f89f3/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20191027093000-83d349e8ac1a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191027093000-83d349e8ac1a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU= golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA=
golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
@@ -405,6 +437,7 @@ golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361 h1:RIIXAeV6GvDBuADKumTODatUqANFZ+5BPMnzsy4hulY=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -432,6 +465,8 @@ google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBr
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d h1:92D1fum1bJLKSdr11OJ+54YeCMCGYIygTA7R/YZxH5M=
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
@@ -448,6 +483,7 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=

View File

@@ -4,11 +4,13 @@ import "context"
type loggerKey struct{} type loggerKey struct{}
// FromContext returns logger from passed context
func FromContext(ctx context.Context) (Logger, bool) { func FromContext(ctx context.Context) (Logger, bool) {
l, ok := ctx.Value(loggerKey{}).(Logger) l, ok := ctx.Value(loggerKey{}).(Logger)
return l, ok return l, ok
} }
// NewContext stores logger into passed context
func NewContext(ctx context.Context, l Logger) context.Context { func NewContext(ctx context.Context, l Logger) context.Context {
return context.WithValue(ctx, loggerKey{}, l) return context.WithValue(ctx, loggerKey{}, l)
} }

View File

@@ -1,114 +0,0 @@
package logger
import (
"os"
)
type Helper struct {
Logger
fields map[string]interface{}
}
func NewHelper(log Logger) *Helper {
return &Helper{Logger: log}
}
func (h *Helper) Info(args ...interface{}) {
if !h.Logger.Options().Level.Enabled(InfoLevel) {
return
}
h.Logger.Fields(h.fields).Log(InfoLevel, args...)
}
func (h *Helper) Infof(template string, args ...interface{}) {
if !h.Logger.Options().Level.Enabled(InfoLevel) {
return
}
h.Logger.Fields(h.fields).Logf(InfoLevel, template, args...)
}
func (h *Helper) Trace(args ...interface{}) {
if !h.Logger.Options().Level.Enabled(TraceLevel) {
return
}
h.Logger.Fields(h.fields).Log(TraceLevel, args...)
}
func (h *Helper) Tracef(template string, args ...interface{}) {
if !h.Logger.Options().Level.Enabled(TraceLevel) {
return
}
h.Logger.Fields(h.fields).Logf(TraceLevel, template, args...)
}
func (h *Helper) Debug(args ...interface{}) {
if !h.Logger.Options().Level.Enabled(DebugLevel) {
return
}
h.Logger.Fields(h.fields).Log(DebugLevel, args...)
}
func (h *Helper) Debugf(template string, args ...interface{}) {
if !h.Logger.Options().Level.Enabled(DebugLevel) {
return
}
h.Logger.Fields(h.fields).Logf(DebugLevel, template, args...)
}
func (h *Helper) Warn(args ...interface{}) {
if !h.Logger.Options().Level.Enabled(WarnLevel) {
return
}
h.Logger.Fields(h.fields).Log(WarnLevel, args...)
}
func (h *Helper) Warnf(template string, args ...interface{}) {
if !h.Logger.Options().Level.Enabled(WarnLevel) {
return
}
h.Logger.Fields(h.fields).Logf(WarnLevel, template, args...)
}
func (h *Helper) Error(args ...interface{}) {
if !h.Logger.Options().Level.Enabled(ErrorLevel) {
return
}
h.Logger.Fields(h.fields).Log(ErrorLevel, args...)
}
func (h *Helper) Errorf(template string, args ...interface{}) {
if !h.Logger.Options().Level.Enabled(ErrorLevel) {
return
}
h.Logger.Fields(h.fields).Logf(ErrorLevel, template, args...)
}
func (h *Helper) Fatal(args ...interface{}) {
if !h.Logger.Options().Level.Enabled(FatalLevel) {
return
}
h.Logger.Fields(h.fields).Log(FatalLevel, args...)
os.Exit(1)
}
func (h *Helper) Fatalf(template string, args ...interface{}) {
if !h.Logger.Options().Level.Enabled(FatalLevel) {
return
}
h.Logger.Fields(h.fields).Logf(FatalLevel, template, args...)
os.Exit(1)
}
func (h *Helper) WithError(err error) *Helper {
fields := copyFields(h.fields)
fields["error"] = err
return &Helper{Logger: h.Logger, fields: fields}
}
func (h *Helper) WithFields(fields map[string]interface{}) *Helper {
nfields := copyFields(fields)
for k, v := range h.fields {
nfields[k] = v
}
return &Helper{Logger: h.Logger, fields: nfields}
}

View File

@@ -2,7 +2,6 @@ package logger
import ( import (
"fmt" "fmt"
"os"
) )
type Level int8 type Level int8
@@ -19,7 +18,7 @@ const (
WarnLevel WarnLevel
// ErrorLevel level. Logs. Used for errors that should definitely be noted. // ErrorLevel level. Logs. Used for errors that should definitely be noted.
ErrorLevel ErrorLevel
// FatalLevel level. Logs and then calls `logger.Exit(1)`. highest level of severity. // FatalLevel level. Logs and then calls `os.Exit(1)`. highest level of severity.
FatalLevel FatalLevel
) )
@@ -63,64 +62,5 @@ func GetLevel(levelStr string) (Level, error) {
case FatalLevel.String(): case FatalLevel.String():
return FatalLevel, nil return FatalLevel, nil
} }
return InfoLevel, fmt.Errorf("Unknown Level String: '%s', defaulting to InfoLevel", levelStr) return InfoLevel, fmt.Errorf("unknown Level String: '%s', use InfoLevel", levelStr)
}
func Info(args ...interface{}) {
DefaultLogger.Log(InfoLevel, args...)
}
func Infof(template string, args ...interface{}) {
DefaultLogger.Logf(InfoLevel, template, args...)
}
func Trace(args ...interface{}) {
DefaultLogger.Log(TraceLevel, args...)
}
func Tracef(template string, args ...interface{}) {
DefaultLogger.Logf(TraceLevel, template, args...)
}
func Debug(args ...interface{}) {
DefaultLogger.Log(DebugLevel, args...)
}
func Debugf(template string, args ...interface{}) {
DefaultLogger.Logf(DebugLevel, template, args...)
}
func Warn(args ...interface{}) {
DefaultLogger.Log(WarnLevel, args...)
}
func Warnf(template string, args ...interface{}) {
DefaultLogger.Logf(WarnLevel, template, args...)
}
func Error(args ...interface{}) {
DefaultLogger.Log(ErrorLevel, args...)
}
func Errorf(template string, args ...interface{}) {
DefaultLogger.Logf(ErrorLevel, template, args...)
}
func Fatal(args ...interface{}) {
DefaultLogger.Log(FatalLevel, args...)
os.Exit(1)
}
func Fatalf(template string, args ...interface{}) {
DefaultLogger.Logf(FatalLevel, template, args...)
os.Exit(1)
}
// Returns true if the given level is at or lower the current logger level
func V(lvl Level, log Logger) bool {
l := DefaultLogger
if log != nil {
l = log
}
return l.Options().Level <= lvl
} }

View File

@@ -1,45 +1,107 @@
// Package log provides a log interface // Package logger provides a log interface
package logger package logger
var ( var (
// Default logger // DefaultLogger variable
DefaultLogger Logger = NewHelper(NewLogger()) DefaultLogger Logger = NewLogger()
) )
// Logger is a generic logging interface // Logger is a generic logging interface
type Logger interface { type Logger interface {
// Init initialises options // Init initialises options
Init(options ...Option) error Init(opts ...Option) error
// V compare provided verbosity level with current log level // V compare provided verbosity level with current log level
V(level Level) bool V(level Level) bool
// The Logger options // The Logger options
Options() Options Options() Options
// Fields set fields to always be logged // Fields set fields to always be logged
Fields(fields map[string]interface{}) Logger Fields(fields map[string]interface{}) Logger
// Log writes a log entry // Info level message
Log(level Level, v ...interface{}) Info(args ...interface{})
// Logf writes a formatted log entry // Trace level message
Logf(level Level, format string, v ...interface{}) Trace(args ...interface{})
// Debug level message
Debug(args ...interface{})
// Warn level message
Warn(args ...interface{})
// Error level message
Error(args ...interface{})
// Fatal level message
Fatal(args ...interface{})
// Infof level message
Infof(msg string, args ...interface{})
// Tracef level message
Tracef(msg string, args ...interface{})
// Debug level message
Debugf(msg string, args ...interface{})
// Warn level message
Warnf(msg string, args ...interface{})
// Error level message
Errorf(msg string, args ...interface{})
// Fatal level message
Fatalf(msg string, args ...interface{})
// String returns the name of logger // String returns the name of logger
String() string String() string
} }
func Info(args ...interface{}) {
DefaultLogger.Info(args...)
}
func Error(args ...interface{}) {
DefaultLogger.Error(args...)
}
func Debug(args ...interface{}) {
DefaultLogger.Debug(args...)
}
func Warn(args ...interface{}) {
DefaultLogger.Warn(args...)
}
func Trace(args ...interface{}) {
DefaultLogger.Trace(args...)
}
func Fatal(args ...interface{}) {
DefaultLogger.Fatal(args...)
}
func Infof(msg string, args ...interface{}) {
DefaultLogger.Infof(msg, args...)
}
func Errorf(msg string, args ...interface{}) {
DefaultLogger.Errorf(msg, args...)
}
func Debugf(msg string, args ...interface{}) {
DefaultLogger.Debugf(msg, args...)
}
func Warnf(msg string, args ...interface{}) {
DefaultLogger.Warnf(msg, args...)
}
func Tracef(msg string, args ...interface{}) {
DefaultLogger.Tracef(msg, args...)
}
func Fatalf(msg string, args ...interface{}) {
DefaultLogger.Fatalf(msg, args...)
}
func V(level Level) bool {
return DefaultLogger.V(level)
}
// Init initialize logger
func Init(opts ...Option) error { func Init(opts ...Option) error {
return DefaultLogger.Init(opts...) return DefaultLogger.Init(opts...)
} }
// Fields create logger with specific fields
func Fields(fields map[string]interface{}) Logger { func Fields(fields map[string]interface{}) Logger {
return DefaultLogger.Fields(fields) return DefaultLogger.Fields(fields)
} }
func Log(level Level, v ...interface{}) {
DefaultLogger.Log(level, v...)
}
func Logf(level Level, format string, v ...interface{}) {
DefaultLogger.Logf(level, format, v...)
}
func String() string {
return DefaultLogger.String()
}

View File

@@ -6,13 +6,11 @@ import (
func TestLogger(t *testing.T) { func TestLogger(t *testing.T) {
l := NewLogger(WithLevel(TraceLevel)) l := NewLogger(WithLevel(TraceLevel))
h1 := NewHelper(l).WithFields(map[string]interface{}{"key1": "val1"}) if err := l.Init(); err != nil {
h1.Trace("trace_msg1") t.Fatal(err)
h1.Warn("warn_msg1") }
l.Trace("trace_msg1")
h2 := NewHelper(l).WithFields(map[string]interface{}{"key2": "val2"}) l.Warn("warn_msg1")
h2.Trace("trace_msg2") l.Fields(map[string]interface{}{"error": "test"}).Info("error message")
h2.Warn("warn_msg2") l.Warn("first", " ", "second")
l.Fields(map[string]interface{}{"key3": "val4"}).Log(InfoLevel, "test_msg")
} }

View File

@@ -1,16 +1,13 @@
package logger package logger
import ( import (
"context" "encoding/json"
"fmt" "fmt"
"os" "os"
"runtime" "runtime"
"sort"
"strings" "strings"
"sync" "sync"
"time" "time"
dlog "github.com/unistack-org/micro/v3/debug/log"
) )
func init() { func init() {
@@ -19,31 +16,33 @@ func init() {
lvl = InfoLevel lvl = InfoLevel
} }
DefaultLogger = NewHelper(NewLogger(WithLevel(lvl))) DefaultLogger = NewLogger(WithLevel(lvl))
} }
type defaultLogger struct { type defaultLogger struct {
sync.RWMutex sync.RWMutex
opts Options opts Options
enc *json.Encoder
} }
// Init(opts...) should only overwrite provided options // Init(opts...) should only overwrite provided options
func (l *defaultLogger) Init(opts ...Option) error { func (l *defaultLogger) Init(opts ...Option) error {
l.Lock()
defer l.Unlock()
for _, o := range opts { for _, o := range opts {
o(&l.opts) o(&l.opts)
} }
l.enc = json.NewEncoder(l.opts.Out)
return nil return nil
} }
func (l *defaultLogger) String() string { func (l *defaultLogger) String() string {
return "default" return "micro"
} }
func (l *defaultLogger) V(level Level) bool { func (l *defaultLogger) V(level Level) bool {
if l.opts.Level.Enabled(level) { return l.opts.Level.Enabled(level)
return true
}
return false
} }
func (l *defaultLogger) Fields(fields map[string]interface{}) Logger { func (l *defaultLogger) Fields(fields map[string]interface{}) Logger {
@@ -85,45 +84,57 @@ func logCallerfilePath(loggingFilePath string) string {
return loggingFilePath[idx+1:] return loggingFilePath[idx+1:]
} }
func (l *defaultLogger) Log(level Level, v ...interface{}) { func (l *defaultLogger) Info(args ...interface{}) {
if !l.V(level) { l.log(InfoLevel, args...)
return
}
l.RLock()
fields := copyFields(l.opts.Fields)
l.RUnlock()
fields["level"] = level.String()
if _, file, line, ok := runtime.Caller(l.opts.CallerSkipCount); ok {
fields["file"] = fmt.Sprintf("%s:%d", logCallerfilePath(file), line)
}
rec := dlog.Record{
Timestamp: time.Now(),
Message: fmt.Sprint(v...),
Metadata: make(map[string]string, len(fields)),
}
keys := make([]string, 0, len(fields))
for k, v := range fields {
keys = append(keys, k)
rec.Metadata[k] = fmt.Sprintf("%v", v)
}
sort.Strings(keys)
metadata := ""
for _, k := range keys {
metadata += fmt.Sprintf(" %s=%v", k, fields[k])
}
t := rec.Timestamp.Format("2006-01-02 15:04:05")
fmt.Printf("%s %s %v\n", t, metadata, rec.Message)
} }
func (l *defaultLogger) Logf(level Level, format string, v ...interface{}) { func (l *defaultLogger) Error(args ...interface{}) {
l.log(ErrorLevel, args...)
}
func (l *defaultLogger) Debug(args ...interface{}) {
l.log(DebugLevel, args...)
}
func (l *defaultLogger) Warn(args ...interface{}) {
l.log(WarnLevel, args...)
}
func (l *defaultLogger) Trace(args ...interface{}) {
l.log(TraceLevel, args...)
}
func (l *defaultLogger) Fatal(args ...interface{}) {
l.log(FatalLevel, args...)
os.Exit(1)
}
func (l *defaultLogger) Infof(msg string, args ...interface{}) {
l.logf(InfoLevel, msg, args...)
}
func (l *defaultLogger) Errorf(msg string, args ...interface{}) {
l.logf(ErrorLevel, msg, args...)
}
func (l *defaultLogger) Debugf(msg string, args ...interface{}) {
l.logf(DebugLevel, msg, args...)
}
func (l *defaultLogger) Warnf(msg string, args ...interface{}) {
l.logf(WarnLevel, msg, args...)
}
func (l *defaultLogger) Tracef(msg string, args ...interface{}) {
l.logf(TraceLevel, msg, args...)
}
func (l *defaultLogger) Fatalf(msg string, args ...interface{}) {
l.logf(FatalLevel, msg, args...)
os.Exit(1)
}
func (l *defaultLogger) log(level Level, args ...interface{}) {
if !l.V(level) { if !l.V(level) {
return return
} }
@@ -135,30 +146,41 @@ func (l *defaultLogger) Logf(level Level, format string, v ...interface{}) {
fields["level"] = level.String() fields["level"] = level.String()
if _, file, line, ok := runtime.Caller(l.opts.CallerSkipCount); ok { if _, file, line, ok := runtime.Caller(l.opts.CallerSkipCount); ok {
fields["file"] = fmt.Sprintf("%s:%d", logCallerfilePath(file), line) fields["caller"] = fmt.Sprintf("%s:%d", logCallerfilePath(file), line)
} }
rec := dlog.Record{ fields["timestamp"] = time.Now().Format("2006-01-02 15:04:05")
Timestamp: time.Now(), fields["msg"] = fmt.Sprint(args...)
Message: fmt.Sprintf(format, v...),
Metadata: make(map[string]string, len(fields)), l.RLock()
_ = l.enc.Encode(fields)
l.RUnlock()
}
func (l *defaultLogger) logf(level Level, msg string, args ...interface{}) {
if !l.V(level) {
return
} }
keys := make([]string, 0, len(fields)) l.RLock()
for k, v := range fields { fields := copyFields(l.opts.Fields)
keys = append(keys, k) l.RUnlock()
rec.Metadata[k] = fmt.Sprintf("%v", v)
fields["level"] = level.String()
if _, file, line, ok := runtime.Caller(l.opts.CallerSkipCount); ok {
fields["caller"] = fmt.Sprintf("%s:%d", logCallerfilePath(file), line)
} }
sort.Strings(keys) fields["timestamp"] = time.Now().Format("2006-01-02 15:04:05")
metadata := "" if len(args) > 0 {
fields["msg"] = fmt.Sprintf(msg, args...)
for _, k := range keys { } else {
metadata += fmt.Sprintf(" %s=%v", k, fields[k]) fields["msg"] = msg
} }
l.RLock()
t := rec.Timestamp.Format("2006-01-02 15:04:05") _ = l.enc.Encode(fields)
fmt.Printf("%s %s %v\n", t, metadata, rec.Message) l.RUnlock()
} }
func (l *defaultLogger) Options() Options { func (l *defaultLogger) Options() Options {
@@ -172,19 +194,7 @@ func (l *defaultLogger) Options() Options {
// NewLogger builds a new logger based on options // NewLogger builds a new logger based on options
func NewLogger(opts ...Option) Logger { func NewLogger(opts ...Option) Logger {
// Default options l := &defaultLogger{opts: NewOptions(opts...)}
options := Options{ l.enc = json.NewEncoder(l.opts.Out)
Level: InfoLevel,
Fields: make(map[string]interface{}),
Out: os.Stderr,
CallerSkipCount: 2,
Context: context.Background(),
}
l := &defaultLogger{opts: options}
if err := l.Init(opts...); err != nil {
l.Log(FatalLevel, err)
}
return l return l
} }

View File

@@ -3,6 +3,7 @@ package logger
import ( import (
"context" "context"
"io" "io"
"os"
) )
type Option func(*Options) type Option func(*Options)
@@ -20,6 +21,20 @@ type Options struct {
Context context.Context Context context.Context
} }
func NewOptions(opts ...Option) Options {
options := Options{
Level: InfoLevel,
Fields: make(map[string]interface{}),
Out: os.Stderr,
CallerSkipCount: 2,
Context: context.Background(),
}
for _, o := range opts {
o(&options)
}
return options
}
// WithFields set default fields for the logger // WithFields set default fields for the logger
func WithFields(fields map[string]interface{}) Option { func WithFields(fields map[string]interface{}) Option {
return func(args *Options) { return func(args *Options) {

View File

@@ -3,7 +3,7 @@ package metadata
import ( import (
"context" "context"
"strings" "net/textproto"
) )
type metadataKey struct{} type metadataKey struct{}
@@ -13,54 +13,63 @@ type metadataKey struct{}
// from Transport headers. // from Transport headers.
type Metadata map[string]string type Metadata map[string]string
func (md Metadata) Get(key string) (string, bool) { var (
// attempt to get as is // DefaultMetadataSize used when need to init new Metadata
val, ok := md[key] DefaultMetadataSize = 6
if ok { )
return val, ok
}
// attempt to get lower case // Get returns value from metadata by key
val, ok = md[strings.Title(key)] func (md Metadata) Get(key string) (string, bool) {
// fast path
val, ok := md[key]
if !ok {
// slow path
val, ok = md[textproto.CanonicalMIMEHeaderKey(key)]
}
return val, ok return val, ok
} }
// Set is used to store value in metadata
func (md Metadata) Set(key, val string) { func (md Metadata) Set(key, val string) {
md[key] = val md[textproto.CanonicalMIMEHeaderKey(key)] = val
} }
func (md Metadata) Delete(key string) { // Del is used to remove value from metadata
// delete key as-is func (md Metadata) Del(key string) {
delete(md, key) // fast path
// delete also Title key if _, ok := md[key]; ok {
delete(md, strings.Title(key)) delete(md, key)
} else {
// slow path
delete(md, textproto.CanonicalMIMEHeaderKey(key))
}
} }
// Copy makes a copy of the metadata // Copy makes a copy of the metadata
func Copy(md Metadata) Metadata { func Copy(md Metadata) Metadata {
cmd := make(Metadata, len(md)) nmd := New(len(md))
for k, v := range md { for key, val := range md {
cmd[k] = v nmd.Set(key, val)
} }
return cmd return nmd
} }
// Delete key from metadata func Del(ctx context.Context, key string) context.Context {
func Delete(ctx context.Context, k string) context.Context { md, ok := FromContext(ctx)
return Set(ctx, k, "") if !ok {
md = New(0)
}
md.Del(key)
return context.WithValue(ctx, metadataKey{}, md)
} }
// Set add key with val to metadata // Set add key with val to metadata
func Set(ctx context.Context, k, v string) context.Context { func Set(ctx context.Context, key, val string) context.Context {
md, ok := FromContext(ctx) md, ok := FromContext(ctx)
if !ok { if !ok {
md = make(Metadata) md = New(0)
}
if v == "" {
delete(md, k)
} else {
md[k] = v
} }
md.Set(key, val)
return context.WithValue(ctx, metadataKey{}, md) return context.WithValue(ctx, metadataKey{}, md)
} }
@@ -70,57 +79,56 @@ func Get(ctx context.Context, key string) (string, bool) {
if !ok { if !ok {
return "", ok return "", ok
} }
// attempt to get as is return md.Get(key)
val, ok := md[key]
if ok {
return val, ok
}
// attempt to get lower case
val, ok = md[strings.Title(key)]
return val, ok
} }
// FromContext returns metadata from the given context // FromContext returns metadata from the given context
func FromContext(ctx context.Context) (Metadata, bool) { func FromContext(ctx context.Context) (Metadata, bool) {
if ctx == nil {
return nil, false
}
md, ok := ctx.Value(metadataKey{}).(Metadata) md, ok := ctx.Value(metadataKey{}).(Metadata)
if !ok { if !ok {
return nil, ok return nil, ok
} }
nmd := Copy(md)
return nmd, ok
}
// capitalise all values // New return new sized metadata
newMD := make(Metadata, len(md)) func New(size int) Metadata {
for k, v := range md { if size == 0 {
newMD[strings.Title(k)] = v size = DefaultMetadataSize
} }
return make(Metadata, size)
return newMD, ok
} }
// NewContext creates a new context with the given metadata // NewContext creates a new context with the given metadata
func NewContext(ctx context.Context, md Metadata) context.Context { func NewContext(ctx context.Context, md Metadata) context.Context {
return context.WithValue(ctx, metadataKey{}, md)
}
// MergeContext merges metadata to existing metadata, overwriting if specified
func MergeContext(ctx context.Context, patchMd Metadata, overwrite bool) context.Context {
if ctx == nil { if ctx == nil {
ctx = context.Background() ctx = context.Background()
} }
md, _ := ctx.Value(metadataKey{}).(Metadata) return context.WithValue(ctx, metadataKey{}, Copy(md))
cmd := make(Metadata, len(md)) }
for k, v := range md {
cmd[k] = v // MergeContext merges metadata to existing metadata, overwriting if specified
func MergeContext(ctx context.Context, pmd Metadata, overwrite bool) context.Context {
if ctx == nil {
ctx = context.Background()
} }
for k, v := range patchMd { md, ok := FromContext(ctx)
if _, ok := cmd[k]; ok && !overwrite { if !ok {
return context.WithValue(ctx, metadataKey{}, Copy(pmd))
}
nmd := Copy(md)
for key, val := range pmd {
if _, ok := nmd[key]; ok && !overwrite {
// skip // skip
} else if v != "" { } else if val != "" {
cmd[k] = v nmd.Set(key, val)
} else { } else {
delete(cmd, k) nmd.Del(key)
} }
} }
return context.WithValue(ctx, metadataKey{}, cmd) return context.WithValue(ctx, metadataKey{}, nmd)
} }

View File

@@ -6,6 +6,30 @@ import (
"testing" "testing"
) )
func TestMedataCanonicalKey(t *testing.T) {
ctx := Set(context.TODO(), "x-request-id", "12345")
v, ok := Get(ctx, "x-request-id")
if !ok {
t.Fatalf("failed to get x-request-id")
} else if v != "12345" {
t.Fatalf("invalid metadata value: %s != %s", "12345", v)
}
v, ok = Get(ctx, "X-Request-Id")
if !ok {
t.Fatalf("failed to get x-request-id")
} else if v != "12345" {
t.Fatalf("invalid metadata value: %s != %s", "12345", v)
}
v, ok = Get(ctx, "X-Request-ID")
if !ok {
t.Fatalf("failed to get x-request-id")
} else if v != "12345" {
t.Fatalf("invalid metadata value: %s != %s", "12345", v)
}
}
func TestMetadataSet(t *testing.T) { func TestMetadataSet(t *testing.T) {
ctx := Set(context.TODO(), "Key", "val") ctx := Set(context.TODO(), "Key", "val")
@@ -25,7 +49,7 @@ func TestMetadataDelete(t *testing.T) {
} }
ctx := NewContext(context.TODO(), md) ctx := NewContext(context.TODO(), md)
ctx = Delete(ctx, "Baz") ctx = Del(ctx, "Baz")
emd, ok := FromContext(ctx) emd, ok := FromContext(ctx)
if !ok { if !ok {
@@ -39,10 +63,19 @@ func TestMetadataDelete(t *testing.T) {
} }
func TestNilContext(t *testing.T) {
var ctx context.Context
_, ok := FromContext(ctx)
if ok {
t.Fatal("nil context")
}
}
func TestMetadataCopy(t *testing.T) { func TestMetadataCopy(t *testing.T) {
md := Metadata{ md := Metadata{
"Foo": "bar", "Foo": "bar",
"bar": "baz", "Bar": "baz",
} }
cp := Copy(md) cp := Copy(md)

View File

@@ -7,7 +7,7 @@ import "time"
type Tags map[string]string type Tags map[string]string
var ( var (
Defaultreporter Reporter DefaultReporter Reporter
) )
// Reporter is an interface for collecting and instrumenting metrics // Reporter is an interface for collecting and instrumenting metrics

View File

@@ -4,6 +4,7 @@ package micro
import ( import (
"context" "context"
"github.com/unistack-org/micro/v3/broker"
"github.com/unistack-org/micro/v3/client" "github.com/unistack-org/micro/v3/client"
"github.com/unistack-org/micro/v3/server" "github.com/unistack-org/micro/v3/server"
) )
@@ -17,13 +18,15 @@ type Service interface {
// The service name // The service name
Name() string Name() string
// Init initialises options // Init initialises options
Init(...Option) Init(...Option) error
// Options returns the current options // Options returns the current options
Options() Options Options() Options
// Client is used to call services // Client is used to call services
Client() client.Client Client() client.Client
// Server is for handling requests and events // Server is for handling requests and events
Server() server.Server Server() server.Server
// Broker is for broker usage
Broker() broker.Broker
// Run the service // Run the service
Run() error Run() error
// The service implementation // The service implementation
@@ -68,10 +71,8 @@ type Event interface {
Publish(ctx context.Context, msg interface{}, opts ...client.PublishOption) error Publish(ctx context.Context, msg interface{}, opts ...client.PublishOption) error
} }
// Type alias to satisfy the deprecation
type Publisher = Event
var ( var (
// HeaderPrefix for all headers passed
HeaderPrefix = "Micro-" HeaderPrefix = "Micro-"
) )
@@ -101,11 +102,6 @@ func NewEvent(topic string, c client.Client) Event {
return &event{c, topic} return &event{c, topic}
} }
// Deprecated: NewPublisher returns a new Publisher
func NewPublisher(topic string, c client.Client) Event {
return NewEvent(topic, c)
}
// RegisterHandler is syntactic sugar for registering a handler // RegisterHandler is syntactic sugar for registering a handler
func RegisterHandler(s server.Server, h interface{}, opts ...server.HandlerOption) error { func RegisterHandler(s server.Server, h interface{}, opts ...server.HandlerOption) error {
return s.Handle(s.NewHandler(h, opts...)) return s.Handle(s.NewHandler(h, opts...))

View File

@@ -3,11 +3,12 @@ package network
import ( import (
"github.com/google/uuid" "github.com/google/uuid"
"github.com/unistack-org/micro/v3/logger" "github.com/unistack-org/micro/v3/logger"
"github.com/unistack-org/micro/v3/network/tunnel"
"github.com/unistack-org/micro/v3/proxy" "github.com/unistack-org/micro/v3/proxy"
"github.com/unistack-org/micro/v3/router" "github.com/unistack-org/micro/v3/router"
"github.com/unistack-org/micro/v3/tunnel"
) )
// Option func
type Option func(*Options) type Option func(*Options)
// Options configure network // Options configure network

76
network/transport/noop.go Normal file
View File

@@ -0,0 +1,76 @@
package transport
import "context"
type noopTransport struct {
opts Options
}
func NewTransport(opts ...Option) Transport {
return &noopTransport{opts: NewOptions(opts...)}
}
func (t *noopTransport) Init(opts ...Option) error {
for _, o := range opts {
o(&t.opts)
}
return nil
}
func (t *noopTransport) Options() Options {
return t.opts
}
func (t *noopTransport) Dial(ctx context.Context, addr string, opts ...DialOption) (Client, error) {
options := NewDialOptions(opts...)
return &noopClient{opts: options}, nil
}
func (t *noopTransport) Listen(ctx context.Context, addr string, opts ...ListenOption) (Listener, error) {
options := NewListenOptions(opts...)
return &noopListener{opts: options}, nil
}
func (t *noopTransport) String() string {
return "noop"
}
type noopClient struct {
opts DialOptions
}
func (c *noopClient) Close() error {
return nil
}
func (c *noopClient) Local() string {
return ""
}
func (c *noopClient) Remote() string {
return ""
}
func (c *noopClient) Recv(*Message) error {
return nil
}
func (c *noopClient) Send(*Message) error {
return nil
}
type noopListener struct {
opts ListenOptions
}
func (l *noopListener) Addr() string {
return ""
}
func (l *noopListener) Accept(fn func(Socket)) error {
return nil
}
func (l *noopListener) Close() error {
return nil
}

View File

@@ -31,6 +31,21 @@ type Options struct {
Context context.Context Context context.Context
} }
// NewOptions returns new options
func NewOptions(opts ...Option) Options {
options := Options{
Logger: logger.DefaultLogger,
Context: context.Background(),
}
for _, o := range opts {
o(&options)
}
return options
}
// DialOptions struct
type DialOptions struct { type DialOptions struct {
// Tells the transport this is a streaming connection with // Tells the transport this is a streaming connection with
// multiple calls to send/recv and that send may not even be called // multiple calls to send/recv and that send may not even be called
@@ -46,6 +61,21 @@ type DialOptions struct {
Context context.Context Context context.Context
} }
// NewDialOptions returns new DialOptions
func NewDialOptions(opts ...DialOption) DialOptions {
options := DialOptions{
Timeout: DefaultDialTimeout,
Context: context.Background(),
}
for _, o := range opts {
o(&options)
}
return options
}
// ListenOptions struct
type ListenOptions struct { type ListenOptions struct {
// TODO: add tls options when listening // TODO: add tls options when listening
// Currently set in global options // Currently set in global options
@@ -55,6 +85,19 @@ type ListenOptions struct {
Context context.Context Context context.Context
} }
// NewListenOptions returns new ListenOptions
func NewListenOptions(opts ...ListenOption) ListenOptions {
options := ListenOptions{
Context: context.Background(),
}
for _, o := range opts {
o(&options)
}
return options
}
// Addrs to use for transport // Addrs to use for transport
func Addrs(addrs ...string) Option { func Addrs(addrs ...string) Option {
return func(o *Options) { return func(o *Options) {
@@ -69,6 +112,13 @@ func Logger(l logger.Logger) Option {
} }
} }
// Context sets the context
func Context(ctx context.Context) Option {
return func(o *Options) {
o.Context = ctx
}
}
// Codec sets the codec used for encoding where the transport // Codec sets the codec used for encoding where the transport
// does not support message headers // does not support message headers
func Codec(c codec.Marshaler) Option { func Codec(c codec.Marshaler) Option {
@@ -99,14 +149,14 @@ func TLSConfig(t *tls.Config) Option {
} }
} }
// Indicates whether this is a streaming connection // WithStream indicates whether this is a streaming connection
func WithStream() DialOption { func WithStream() DialOption {
return func(o *DialOptions) { return func(o *DialOptions) {
o.Stream = true o.Stream = true
} }
} }
// Timeout used when dialling the remote side // WithTimeout used when dialling the remote side
func WithTimeout(d time.Duration) DialOption { func WithTimeout(d time.Duration) DialOption {
return func(o *DialOptions) { return func(o *DialOptions) {
o.Timeout = d o.Timeout = d

View File

@@ -2,11 +2,15 @@
package transport package transport
import ( import (
"context"
"time" "time"
) )
var ( var (
DefaultTransport Transport // DefaultTransport is the global default transport
DefaultTransport Transport = NewTransport()
// Default dial timeout
DefaultDialTimeout = time.Second * 5
) )
// Transport is an interface which is used for communication between // Transport is an interface which is used for communication between
@@ -15,8 +19,8 @@ var (
type Transport interface { type Transport interface {
Init(...Option) error Init(...Option) error
Options() Options Options() Options
Dial(addr string, opts ...DialOption) (Client, error) Dial(ctx context.Context, addr string, opts ...DialOption) (Client, error)
Listen(addr string, opts ...ListenOption) (Listener, error) Listen(ctx context.Context, addr string, opts ...ListenOption) (Listener, error)
String() string String() string
} }
@@ -55,8 +59,3 @@ type DialOption func(*DialOptions)
// ListenOption is the option signature // ListenOption is the option signature
type ListenOption func(*ListenOptions) type ListenOption func(*ListenOptions)
var (
// Default dial timeout
DefaultDialTimeout = time.Second * 5
)

View File

@@ -7,8 +7,8 @@ import (
"github.com/unistack-org/micro/v3/broker" "github.com/unistack-org/micro/v3/broker"
"github.com/unistack-org/micro/v3/logger" "github.com/unistack-org/micro/v3/logger"
"github.com/unistack-org/micro/v3/transport" "github.com/unistack-org/micro/v3/network/transport"
"github.com/unistack-org/micro/v3/tunnel" "github.com/unistack-org/micro/v3/network/tunnel"
) )
type tunBroker struct { type tunBroker struct {
@@ -49,18 +49,18 @@ func (t *tunBroker) Address() string {
return t.tunnel.Address() return t.tunnel.Address()
} }
func (t *tunBroker) Connect() error { func (t *tunBroker) Connect(ctx context.Context) error {
return t.tunnel.Connect() return t.tunnel.Connect(ctx)
} }
func (t *tunBroker) Disconnect() error { func (t *tunBroker) Disconnect(ctx context.Context) error {
return t.tunnel.Close() return t.tunnel.Close(ctx)
} }
func (t *tunBroker) Publish(topic string, m *broker.Message, opts ...broker.PublishOption) error { func (t *tunBroker) Publish(ctx context.Context, topic string, m *broker.Message, opts ...broker.PublishOption) error {
// TODO: this is probably inefficient, we might want to just maintain an open connection // TODO: this is probably inefficient, we might want to just maintain an open connection
// it may be easier to add broadcast to the tunnel // it may be easier to add broadcast to the tunnel
c, err := t.tunnel.Dial(topic, tunnel.DialMode(tunnel.Multicast)) c, err := t.tunnel.Dial(ctx, topic, tunnel.DialMode(tunnel.Multicast))
if err != nil { if err != nil {
return err return err
} }
@@ -72,21 +72,16 @@ func (t *tunBroker) Publish(topic string, m *broker.Message, opts ...broker.Publ
}) })
} }
func (t *tunBroker) Subscribe(topic string, h broker.Handler, opts ...broker.SubscribeOption) (broker.Subscriber, error) { func (t *tunBroker) Subscribe(ctx context.Context, topic string, h broker.Handler, opts ...broker.SubscribeOption) (broker.Subscriber, error) {
l, err := t.tunnel.Listen(topic, tunnel.ListenMode(tunnel.Multicast)) l, err := t.tunnel.Listen(ctx, topic, tunnel.ListenMode(tunnel.Multicast))
if err != nil { if err != nil {
return nil, err return nil, err
} }
var options broker.SubscribeOptions
for _, o := range opts {
o(&options)
}
tunSub := &tunSubscriber{ tunSub := &tunSubscriber{
topic: topic, topic: topic,
handler: h, handler: h,
opts: options, opts: broker.NewSubscribeOptions(opts...),
closed: make(chan bool), closed: make(chan bool),
listener: l, listener: l,
} }
@@ -117,9 +112,13 @@ func (t *tunSubscriber) run() {
// receive message // receive message
m := new(transport.Message) m := new(transport.Message)
if err := c.Recv(m); err != nil { if err := c.Recv(m); err != nil {
logger.Error(err) if logger.V(logger.ErrorLevel) {
logger.Error(err.Error())
}
if err = c.Close(); err != nil { if err = c.Close(); err != nil {
logger.Error(err) if logger.V(logger.ErrorLevel) {
logger.Error(err.Error())
}
} }
continue continue
} }
@@ -146,7 +145,7 @@ func (t *tunSubscriber) Topic() string {
return t.topic return t.topic
} }
func (t *tunSubscriber) Unsubscribe() error { func (t *tunSubscriber) Unsubscribe(ctx context.Context) error {
select { select {
case <-t.closed: case <-t.closed:
return nil return nil
@@ -173,12 +172,7 @@ func (t *tunEvent) Error() error {
} }
func NewBroker(opts ...broker.Option) (broker.Broker, error) { func NewBroker(opts ...broker.Option) (broker.Broker, error) {
options := broker.Options{ options := broker.NewOptions(opts...)
Context: context.Background(),
}
for _, o := range opts {
o(&options)
}
t, ok := options.Context.Value(tunnelKey{}).(tunnel.Tunnel) t, ok := options.Context.Value(tunnelKey{}).(tunnel.Tunnel)
if !ok { if !ok {

View File

@@ -5,7 +5,7 @@ import (
"github.com/google/uuid" "github.com/google/uuid"
"github.com/unistack-org/micro/v3/logger" "github.com/unistack-org/micro/v3/logger"
"github.com/unistack-org/micro/v3/transport" "github.com/unistack-org/micro/v3/network/transport"
) )
var ( var (
@@ -15,6 +15,7 @@ var (
DefaultToken = "go.micro.tunnel" DefaultToken = "go.micro.tunnel"
) )
// Option func
type Option func(*Options) type Option func(*Options)
// Options provides network configuration options // Options provides network configuration options
@@ -33,8 +34,10 @@ type Options struct {
Logger logger.Logger Logger logger.Logger
} }
// DialOption func
type DialOption func(*DialOptions) type DialOption func(*DialOptions)
// DialOptions provides dial options
type DialOptions struct { type DialOptions struct {
// Link specifies the link to use // Link specifies the link to use
Link string Link string
@@ -46,8 +49,10 @@ type DialOptions struct {
Timeout time.Duration Timeout time.Duration
} }
// ListenOption func
type ListenOption func(*ListenOptions) type ListenOption func(*ListenOptions)
// ListenOptions provides listen options
type ListenOptions struct { type ListenOptions struct {
// specify mode of the session // specify mode of the session
Mode Mode Mode Mode
@@ -55,7 +60,7 @@ type ListenOptions struct {
Timeout time.Duration Timeout time.Duration
} }
// The tunnel id // Id sets the tunnel id
func Id(id string) Option { func Id(id string) Option {
return func(o *Options) { return func(o *Options) {
o.Id = id o.Id = id
@@ -69,7 +74,7 @@ func Logger(l logger.Logger) Option {
} }
} }
// The tunnel address // Address sets the tunnel address
func Address(a string) Option { func Address(a string) Option {
return func(o *Options) { return func(o *Options) {
o.Address = a o.Address = a
@@ -97,23 +102,21 @@ func Transport(t transport.Transport) Option {
} }
} }
// Listen options // ListenMode option
func ListenMode(m Mode) ListenOption { func ListenMode(m Mode) ListenOption {
return func(o *ListenOptions) { return func(o *ListenOptions) {
o.Mode = m o.Mode = m
} }
} }
// Timeout for reads and writes on the listener session // ListenTimeout for reads and writes on the listener session
func ListenTimeout(t time.Duration) ListenOption { func ListenTimeout(t time.Duration) ListenOption {
return func(o *ListenOptions) { return func(o *ListenOptions) {
o.Timeout = t o.Timeout = t
} }
} }
// Dial options // DialMode multicast sets the multicast option to send only to those mapped
// Dial multicast sets the multicast option to send only to those mapped
func DialMode(m Mode) DialOption { func DialMode(m Mode) DialOption {
return func(o *DialOptions) { return func(o *DialOptions) {
o.Mode = m o.Mode = m
@@ -144,10 +147,14 @@ func DialWait(b bool) DialOption {
} }
// DefaultOptions returns router default options // DefaultOptions returns router default options
func DefaultOptions() Options { func NewOptions(opts ...Option) Options {
return Options{ options := Options{
Id: uuid.New().String(), Id: uuid.New().String(),
Address: DefaultAddress, Address: DefaultAddress,
Token: DefaultToken, Token: DefaultToken,
} }
for _, o := range opts {
o(&options)
}
return options
} }

View File

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

View File

@@ -5,8 +5,8 @@ import (
"context" "context"
"fmt" "fmt"
"github.com/unistack-org/micro/v3/transport" "github.com/unistack-org/micro/v3/network/transport"
"github.com/unistack-org/micro/v3/tunnel" "github.com/unistack-org/micro/v3/network/tunnel"
) )
type tunTransport struct { type tunTransport struct {
@@ -26,7 +26,7 @@ func (t *tunTransport) Init(opts ...transport.Option) error {
// close the existing tunnel // close the existing tunnel
if t.tunnel != nil { if t.tunnel != nil {
t.tunnel.Close() t.tunnel.Close(context.TODO())
} }
// get the tunnel // get the tunnel
@@ -47,12 +47,12 @@ func (t *tunTransport) Init(opts ...transport.Option) error {
return nil return nil
} }
func (t *tunTransport) Dial(addr string, opts ...transport.DialOption) (transport.Client, error) { func (t *tunTransport) Dial(ctx context.Context, addr string, opts ...transport.DialOption) (transport.Client, error) {
if err := t.tunnel.Connect(); err != nil { if err := t.tunnel.Connect(ctx); err != nil {
return nil, err return nil, err
} }
c, err := t.tunnel.Dial(addr) c, err := t.tunnel.Dial(ctx, addr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -60,12 +60,12 @@ func (t *tunTransport) Dial(addr string, opts ...transport.DialOption) (transpor
return c, nil return c, nil
} }
func (t *tunTransport) Listen(addr string, opts ...transport.ListenOption) (transport.Listener, error) { func (t *tunTransport) Listen(ctx context.Context, addr string, opts ...transport.ListenOption) (transport.Listener, error) {
if err := t.tunnel.Connect(); err != nil { if err := t.tunnel.Connect(ctx); err != nil {
return nil, err return nil, err
} }
l, err := t.tunnel.Listen(addr) l, err := t.tunnel.Listen(ctx, addr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -93,7 +93,7 @@ func NewTransport(opts ...transport.Option) transport.Transport {
return t return t
} }
// WithTransport sets the internal tunnel // WithTunnel sets the internal tunnel
func WithTunnel(t tunnel.Tunnel) transport.Option { func WithTunnel(t tunnel.Tunnel) transport.Option {
return func(o *transport.Options) { return func(o *transport.Options) {
if o.Context == nil { if o.Context == nil {

View File

@@ -2,10 +2,11 @@
package tunnel package tunnel
import ( import (
"context"
"errors" "errors"
"time" "time"
"github.com/unistack-org/micro/v3/transport" "github.com/unistack-org/micro/v3/network/transport"
) )
var ( var (
@@ -55,15 +56,15 @@ type Tunnel interface {
// Address returns the address the tunnel is listening on // Address returns the address the tunnel is listening on
Address() string Address() string
// Connect connects the tunnel // Connect connects the tunnel
Connect() error Connect(ctx context.Context) error
// Close closes the tunnel // Close closes the tunnel
Close() error Close(ctx context.Context) error
// Links returns all the links the tunnel is connected to // Links returns all the links the tunnel is connected to
Links() []Link Links() []Link
// Dial allows a client to connect to a channel // Dial allows a client to connect to a channel
Dial(channel string, opts ...DialOption) (Session, error) Dial(ctx context.Context, channel string, opts ...DialOption) (Session, error)
// Listen allows to accept connections on a channel // Listen allows to accept connections on a channel
Listen(channel string, opts ...ListenOption) (Listener, error) Listen(ctx context.Context, channel string, opts ...ListenOption) (Listener, error)
// String returns the name of the tunnel implementation // String returns the name of the tunnel implementation
String() string String() string
} }

View File

@@ -11,15 +11,15 @@ import (
"github.com/unistack-org/micro/v3/client" "github.com/unistack-org/micro/v3/client"
"github.com/unistack-org/micro/v3/config" "github.com/unistack-org/micro/v3/config"
"github.com/unistack-org/micro/v3/debug/profile" "github.com/unistack-org/micro/v3/debug/profile"
"github.com/unistack-org/micro/v3/debug/trace"
"github.com/unistack-org/micro/v3/logger" "github.com/unistack-org/micro/v3/logger"
"github.com/unistack-org/micro/v3/network/transport"
"github.com/unistack-org/micro/v3/registry" "github.com/unistack-org/micro/v3/registry"
"github.com/unistack-org/micro/v3/router" "github.com/unistack-org/micro/v3/router"
"github.com/unistack-org/micro/v3/runtime" "github.com/unistack-org/micro/v3/runtime"
"github.com/unistack-org/micro/v3/selector" "github.com/unistack-org/micro/v3/selector"
"github.com/unistack-org/micro/v3/server" "github.com/unistack-org/micro/v3/server"
"github.com/unistack-org/micro/v3/store" "github.com/unistack-org/micro/v3/store"
"github.com/unistack-org/micro/v3/transport" "github.com/unistack-org/micro/v3/tracer"
) )
// Options for micro service // Options for micro service
@@ -47,14 +47,12 @@ type Options struct {
// Other options for implementations of the interface // Other options for implementations of the interface
// can be stored in a context // can be stored in a context
Context context.Context Context context.Context
Signal bool
} }
func newOptions(opts ...Option) Options { // NewOptions returns new Options filled with defaults and overrided by provided opts
opt := Options{ func NewOptions(opts ...Option) Options {
options := Options{
Context: context.Background(), Context: context.Background(),
Signal: true,
Server: server.DefaultServer, Server: server.DefaultServer,
Client: client.DefaultClient, Client: client.DefaultClient,
Broker: broker.DefaultBroker, Broker: broker.DefaultBroker,
@@ -70,12 +68,13 @@ func newOptions(opts ...Option) Options {
} }
for _, o := range opts { for _, o := range opts {
o(&opt) o(&options)
} }
return opt return options
} }
// Option func
type Option func(*Options) type Option func(*Options)
// Broker to be used for service // Broker to be used for service
@@ -92,6 +91,7 @@ func Broker(b broker.Broker) Option {
} }
} }
// Cmd to be used for service
func Cmd(c cmd.Cmd) Option { func Cmd(c cmd.Cmd) Option {
return func(o *Options) { return func(o *Options) {
o.Cmd = c o.Cmd = c
@@ -113,15 +113,6 @@ func Context(ctx context.Context) Option {
} }
} }
// HandleSignal toggles automatic installation of the signal handler that
// traps TERM, INT, and QUIT. Users of this feature to disable the signal
// handler, should control liveness of the service through the context.
func HandleSignal(b bool) Option {
return func(o *Options) {
o.Signal = b
}
}
// Profile to be used for debug profile // Profile to be used for debug profile
func Profile(p profile.Profile) Option { func Profile(p profile.Profile) Option {
return func(o *Options) { return func(o *Options) {
@@ -171,7 +162,7 @@ func Registry(r registry.Registry) Option {
} }
// Tracer sets the tracer for the service // Tracer sets the tracer for the service
func Tracer(t trace.Tracer) Option { func Tracer(t tracer.Tracer) Option {
return func(o *Options) { return func(o *Options) {
if o.Server != nil { if o.Server != nil {
//todo client trace //todo client trace

125
registry/extractor.go Normal file
View File

@@ -0,0 +1,125 @@
package registry
import (
"fmt"
"reflect"
"strings"
"github.com/unistack-org/micro/v3/metadata"
)
// Extract *Value from reflect.Type
func ExtractValue(v reflect.Type, d int) *Value {
if d == 3 {
return nil
}
if v == nil {
return nil
}
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
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()
}
return arg
}
// ExtractEndpoint extract *Endpoint from reflect.Method
func ExtractEndpoint(method reflect.Method) *Endpoint {
if method.PkgPath != "" {
return nil
}
var rspType, reqType reflect.Type
var stream bool
mt := method.Type
switch mt.NumIn() {
case 3:
reqType = mt.In(1)
rspType = mt.In(2)
case 4:
reqType = mt.In(2)
rspType = mt.In(3)
default:
return nil
}
// are we dealing with a stream?
switch rspType.Kind() {
case reflect.Func, reflect.Interface:
stream = true
}
request := ExtractValue(reqType, 0)
response := ExtractValue(rspType, 0)
ep := &Endpoint{
Name: method.Name,
Request: request,
Response: response,
Metadata: metadata.New(0),
}
if stream {
ep.Metadata = map[string]string{
"stream": fmt.Sprintf("%v", stream),
}
}
return ep
}
// ExtractSubValue exctact *Value from reflect.Type
func ExtractSubValue(typ reflect.Type) *Value {
var reqType reflect.Type
switch typ.NumIn() {
case 1:
reqType = typ.In(0)
case 2:
reqType = typ.In(1)
case 3:
reqType = typ.In(2)
default:
return nil
}
return ExtractValue(reqType, 0)
}

View File

@@ -0,0 +1,63 @@
package registry
import (
"context"
"reflect"
"testing"
)
type testHandler struct{}
type testRequest struct{}
type testResponse struct{}
func (t *testHandler) Test(ctx context.Context, req *testRequest, rsp *testResponse) error {
return nil
}
func TestExtractEndpoint(t *testing.T) {
handler := &testHandler{}
typ := reflect.TypeOf(handler)
var endpoints []*Endpoint
for m := 0; m < typ.NumMethod(); m++ {
if e := ExtractEndpoint(typ.Method(m)); e != nil {
endpoints = append(endpoints, e)
}
}
if i := len(endpoints); i != 1 {
t.Errorf("Expected 1 endpoint, have %d", i)
}
if endpoints[0].Name != "Test" {
t.Errorf("Expected handler Test, got %s", endpoints[0].Name)
}
if endpoints[0].Request == nil {
t.Error("Expected non nil request")
}
if endpoints[0].Response == nil {
t.Error("Expected non nil request")
}
if endpoints[0].Request.Name != "testRequest" {
t.Errorf("Expected testRequest got %s", endpoints[0].Request.Name)
}
if endpoints[0].Response.Name != "testResponse" {
t.Errorf("Expected testResponse got %s", endpoints[0].Response.Name)
}
if endpoints[0].Request.Type != "testRequest" {
t.Errorf("Expected testRequest type got %s", endpoints[0].Request.Type)
}
if endpoints[0].Response.Type != "testResponse" {
t.Errorf("Expected testResponse type got %s", endpoints[0].Response.Type)
}
}

View File

@@ -1,11 +1,15 @@
package registry package registry
import "fmt" import (
"context"
"fmt"
)
type noopRegistry struct { type noopRegistry struct {
opts Options opts Options
} }
// Init initialize registry
func (n *noopRegistry) Init(opts ...Option) error { func (n *noopRegistry) Init(opts ...Option) error {
for _, o := range opts { for _, o := range opts {
o(&n.opts) o(&n.opts)
@@ -13,41 +17,52 @@ func (n *noopRegistry) Init(opts ...Option) error {
return nil return nil
} }
// Options returns options struct
func (n *noopRegistry) Options() Options { func (n *noopRegistry) Options() Options {
return n.opts return n.opts
} }
func (n *noopRegistry) Register(*Service, ...RegisterOption) error { // Connect opens connection to registry
func (n *noopRegistry) Connect(ctx context.Context) error {
return nil return nil
} }
func (n *noopRegistry) Deregister(*Service, ...DeregisterOption) error { // Disconnect close connection to registry
func (n *noopRegistry) Disconnect(ctx context.Context) error {
return nil return nil
} }
func (n *noopRegistry) GetService(string, ...GetOption) ([]*Service, error) { // Register registers service
func (n *noopRegistry) Register(ctx context.Context, svc *Service, opts ...RegisterOption) error {
return nil
}
// Deregister deregisters service
func (n *noopRegistry) Deregister(ctx context.Context, svc *Service, opts ...DeregisterOption) error {
return nil
}
// GetService returns servive info
func (n *noopRegistry) GetService(ctx context.Context, name string, opts ...GetOption) ([]*Service, error) {
return []*Service{}, nil return []*Service{}, nil
} }
func (n *noopRegistry) ListServices(...ListOption) ([]*Service, error) { // ListServices listing services
func (n *noopRegistry) ListServices(ctx context.Context, opts ...ListOption) ([]*Service, error) {
return []*Service{}, nil return []*Service{}, nil
} }
func (n *noopRegistry) Watch(...WatchOption) (Watcher, error) { // Watch is used to watch for service changes
func (n *noopRegistry) Watch(ctx context.Context, opts ...WatchOption) (Watcher, error) {
return nil, fmt.Errorf("not implemented") return nil, fmt.Errorf("not implemented")
} }
// String returns registry string representation
func (n *noopRegistry) String() string { func (n *noopRegistry) String() string {
return "noop" return "noop"
} }
// newRegistry returns a new noop registry // NewRegistry returns a new noop registry
func newRegistry(opts ...Option) Registry { func NewRegistry(opts ...Option) Registry {
options := NewOptions() return &noopRegistry{opts: NewOptions(opts...)}
for _, o := range opts {
o(&options)
}
return &noopRegistry{opts: options}
} }

View File

@@ -19,11 +19,15 @@ type Options struct {
Context context.Context Context context.Context
} }
func NewOptions() Options { func NewOptions(opts ...Option) Options {
return Options{ options := Options{
Logger: logger.DefaultLogger, Logger: logger.DefaultLogger,
Context: context.Background(), Context: context.Background(),
} }
for _, o := range opts {
o(&options)
}
return options
} }
type RegisterOptions struct { type RegisterOptions struct {
@@ -33,6 +37,19 @@ type RegisterOptions struct {
Context context.Context Context context.Context
// Domain to register the service in // Domain to register the service in
Domain string Domain string
// Attempts specify attempts for register
Attempts int
}
func NewRegisterOptions(opts ...RegisterOption) RegisterOptions {
options := RegisterOptions{
Domain: DefaultDomain,
Context: context.Background(),
}
for _, o := range opts {
o(&options)
}
return options
} }
type WatchOptions struct { type WatchOptions struct {
@@ -46,10 +63,34 @@ type WatchOptions struct {
Domain string Domain string
} }
func NewWatchOptions(opts ...WatchOption) WatchOptions {
options := WatchOptions{
Domain: DefaultDomain,
Context: context.Background(),
}
for _, o := range opts {
o(&options)
}
return options
}
type DeregisterOptions struct { type DeregisterOptions struct {
Context context.Context Context context.Context
// Domain the service was registered in // Domain the service was registered in
Domain string Domain string
// Atempts specify max attempts for deregister
Attempts int
}
func NewDeregisterOptions(opts ...DeregisterOption) DeregisterOptions {
options := DeregisterOptions{
Domain: DefaultDomain,
Context: context.Background(),
}
for _, o := range opts {
o(&options)
}
return options
} }
type GetOptions struct { type GetOptions struct {
@@ -58,12 +99,34 @@ type GetOptions struct {
Domain string Domain string
} }
func NewGetOptions(opts ...GetOption) GetOptions {
options := GetOptions{
Domain: DefaultDomain,
Context: context.Background(),
}
for _, o := range opts {
o(&options)
}
return options
}
type ListOptions struct { type ListOptions struct {
Context context.Context Context context.Context
// Domain to scope the request to // Domain to scope the request to
Domain string Domain string
} }
func NewListOptions(opts ...ListOption) ListOptions {
options := ListOptions{
Domain: DefaultDomain,
Context: context.Background(),
}
for _, o := range opts {
o(&options)
}
return options
}
// Addrs is the registry addresses to use // Addrs is the registry addresses to use
func Addrs(addrs ...string) Option { func Addrs(addrs ...string) Option {
return func(o *Options) { return func(o *Options) {
@@ -91,6 +154,13 @@ func Logger(l logger.Logger) Option {
} }
} }
// Context sets the context
func Context(ctx context.Context) Option {
return func(o *Options) {
o.Context = ctx
}
}
// Specify TLS Config // Specify TLS Config
func TLSConfig(t *tls.Config) Option { func TLSConfig(t *tls.Config) Option {
return func(o *Options) { return func(o *Options) {
@@ -98,6 +168,12 @@ func TLSConfig(t *tls.Config) Option {
} }
} }
func RegisterAttempts(t int) RegisterOption {
return func(o *RegisterOptions) {
o.Attempts = t
}
}
func RegisterTTL(t time.Duration) RegisterOption { func RegisterTTL(t time.Duration) RegisterOption {
return func(o *RegisterOptions) { return func(o *RegisterOptions) {
o.TTL = t o.TTL = t
@@ -135,6 +211,12 @@ func WatchDomain(d string) WatchOption {
} }
} }
func DeregisterTimeout(t int) DeregisterOption {
return func(o *DeregisterOptions) {
o.Attempts = t
}
}
func DeregisterContext(ctx context.Context) DeregisterOption { func DeregisterContext(ctx context.Context) DeregisterOption {
return func(o *DeregisterOptions) { return func(o *DeregisterOptions) {
o.Context = ctx o.Context = ctx

View File

@@ -2,6 +2,7 @@
package registry package registry
import ( import (
"context"
"errors" "errors"
) )
@@ -13,7 +14,8 @@ const (
) )
var ( var (
DefaultRegistry Registry = newRegistry() // DefaultRegistry is the global default registry
DefaultRegistry Registry = NewRegistry()
// ErrNotFound returned when GetService is called and no services found // ErrNotFound returned when GetService is called and no services found
ErrNotFound = errors.New("service not found") ErrNotFound = errors.New("service not found")
// ErrWatcherStopped returned when when watcher is stopped // ErrWatcherStopped returned when when watcher is stopped
@@ -26,11 +28,13 @@ var (
type Registry interface { type Registry interface {
Init(...Option) error Init(...Option) error
Options() Options Options() Options
Register(*Service, ...RegisterOption) error Connect(context.Context) error
Deregister(*Service, ...DeregisterOption) error Disconnect(context.Context) error
GetService(string, ...GetOption) ([]*Service, error) Register(context.Context, *Service, ...RegisterOption) error
ListServices(...ListOption) ([]*Service, error) Deregister(context.Context, *Service, ...DeregisterOption) error
Watch(...WatchOption) (Watcher, error) GetService(context.Context, string, ...GetOption) ([]*Service, error)
ListServices(context.Context, ...ListOption) ([]*Service, error)
Watch(context.Context, ...WatchOption) (Watcher, error)
String() string String() string
} }

View File

@@ -2,6 +2,8 @@
package registry package registry
import ( import (
"context"
"github.com/unistack-org/micro/v3/registry" "github.com/unistack-org/micro/v3/registry"
"github.com/unistack-org/micro/v3/resolver" "github.com/unistack-org/micro/v3/resolver"
) )
@@ -14,7 +16,7 @@ type Resolver struct {
// Resolve assumes ID is a domain name e.g micro.mu // Resolve assumes ID is a domain name e.g micro.mu
func (r *Resolver) Resolve(name string) ([]*resolver.Record, error) { func (r *Resolver) Resolve(name string) ([]*resolver.Record, error) {
services, err := r.Registry.GetService(name) services, err := r.Registry.GetService(context.TODO(), name)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -9,7 +9,7 @@ type Resolver interface {
Resolve(name string) ([]*Record, error) Resolve(name string) ([]*Record, error)
} }
// A resolved record // Record that resolved
type Record struct { type Record struct {
Address string `json:"address"` Address string `json:"address"`
Priority int64 `json:"priority"` Priority int64 `json:"priority"`

View File

@@ -77,11 +77,17 @@ func Precache() Option {
} }
} }
// DefaultOptions returns router default options // NewOptions returns router default options
func DefaultOptions() Options { func NewOptions(opts ...Option) Options {
return Options{ options := Options{
Id: uuid.New().String(), Id: uuid.New().String(),
Network: DefaultNetwork, Network: DefaultNetwork,
Context: context.Background(), Registry: registry.DefaultRegistry,
Logger: logger.DefaultLogger,
Context: context.Background(),
} }
for _, o := range opts {
o(&options)
}
return options
} }

View File

@@ -65,7 +65,7 @@ func QueryLink(link string) QueryOption {
// NewQuery creates new query and returns it // NewQuery creates new query and returns it
func NewQuery(opts ...QueryOption) QueryOptions { func NewQuery(opts ...QueryOption) QueryOptions {
// default options // default options
qopts := QueryOptions{ options := QueryOptions{
Service: "*", Service: "*",
Address: "*", Address: "*",
Gateway: "*", Gateway: "*",
@@ -75,8 +75,8 @@ func NewQuery(opts ...QueryOption) QueryOptions {
} }
for _, o := range opts { for _, o := range opts {
o(&qopts) o(&options)
} }
return qopts return options
} }

View File

@@ -6,6 +6,7 @@ import (
) )
var ( var (
// DefaultRouter is the global default router
DefaultRouter Router DefaultRouter Router
// DefaultNetwork is default micro network // DefaultNetwork is default micro network
DefaultNetwork = "micro" DefaultNetwork = "micro"

View File

@@ -7,6 +7,7 @@ import (
) )
var ( var (
// ErrAlreadyExists error
ErrAlreadyExists = errors.New("already exists") ErrAlreadyExists = errors.New("already exists")
) )

View File

@@ -6,11 +6,33 @@ import (
type serverKey struct{} type serverKey struct{}
// FromContext returns Server from context
func FromContext(ctx context.Context) (Server, bool) { func FromContext(ctx context.Context) (Server, bool) {
c, ok := ctx.Value(serverKey{}).(Server) c, ok := ctx.Value(serverKey{}).(Server)
return c, ok return c, ok
} }
// NewContext stores Server to context
func NewContext(ctx context.Context, s Server) context.Context { func NewContext(ctx context.Context, s Server) context.Context {
return context.WithValue(ctx, serverKey{}, s) return context.WithValue(ctx, serverKey{}, s)
} }
// 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)
}
}
// SetSubscriberOption returns a function to setup a context with given value
func SetSubscriberOption(k, v interface{}) SubscriberOption {
return func(o *SubscriberOptions) {
if o.Context == nil {
o.Context = context.Background()
}
o.Context = context.WithValue(o.Context, k, v)
}
}

View File

@@ -1,80 +1,59 @@
package server package server
import "context" import (
"reflect"
type HandlerOption func(*HandlerOptions) "github.com/unistack-org/micro/v3/registry"
)
type HandlerOptions struct { type rpcHandler struct {
Internal bool name string
Metadata map[string]map[string]string handler interface{}
endpoints []*registry.Endpoint
opts HandlerOptions
} }
type SubscriberOption func(*SubscriberOptions) func newRpcHandler(handler interface{}, opts ...HandlerOption) Handler {
options := NewHandlerOptions(opts...)
type SubscriberOptions struct { typ := reflect.TypeOf(handler)
// AutoAck defaults to true. When a handler returns hdlr := reflect.ValueOf(handler)
// with a nil error the message is acked. name := reflect.Indirect(hdlr).Type().Name()
AutoAck bool
Queue string
Internal bool
Context context.Context
}
// EndpointMetadata is a Handler option that allows metadata to be added to var endpoints []*registry.Endpoint
// individual endpoints.
func EndpointMetadata(name string, md map[string]string) HandlerOption { for m := 0; m < typ.NumMethod(); m++ {
return func(o *HandlerOptions) { if e := registry.ExtractEndpoint(typ.Method(m)); e != nil {
o.Metadata[name] = md e.Name = name + "." + e.Name
for k, v := range options.Metadata[e.Name] {
e.Metadata[k] = v
}
endpoints = append(endpoints, e)
}
}
return &rpcHandler{
name: name,
handler: handler,
endpoints: endpoints,
opts: options,
} }
} }
// Internal Handler options specifies that a handler is not advertised func (r *rpcHandler) Name() string {
// to the discovery system. In the future this may also limit request return r.name
// to the internal network or authorised user.
func InternalHandler(b bool) HandlerOption {
return func(o *HandlerOptions) {
o.Internal = b
}
} }
// Internal Subscriber options specifies that a subscriber is not advertised func (r *rpcHandler) Handler() interface{} {
// to the discovery system. return r.handler
func InternalSubscriber(b bool) SubscriberOption {
return func(o *SubscriberOptions) {
o.Internal = b
}
}
func NewSubscriberOptions(opts ...SubscriberOption) SubscriberOptions {
opt := SubscriberOptions{
AutoAck: true,
Context: context.Background(),
}
for _, o := range opts {
o(&opt)
}
return opt
} }
// DisableAutoAck will disable auto acking of messages func (r *rpcHandler) Endpoints() []*registry.Endpoint {
// after they have been handled. return r.endpoints
func DisableAutoAck() SubscriberOption {
return func(o *SubscriberOptions) {
o.AutoAck = false
}
} }
// Shared queue name distributed messages across subscribers func (r *rpcHandler) Options() HandlerOptions {
func SubscriberQueue(n string) SubscriberOption { return r.opts
return func(o *SubscriberOptions) {
o.Queue = n
}
}
// SubscriberContext set context options to allow broker SubscriberOption passed
func SubscriberContext(ctx context.Context) SubscriberOption {
return func(o *SubscriberOptions) {
o.Context = ctx
}
} }

456
server/noop.go Normal file
View File

@@ -0,0 +1,456 @@
package server
import (
"bytes"
"fmt"
"sort"
"sync"
"time"
craw "github.com/unistack-org/micro-codec-bytes"
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/registry"
)
var (
DefaultCodecs = map[string]codec.NewCodec{
"application/json": cjson.NewCodec,
"application/json-rpc": cjsonrpc.NewCodec,
"application/protobuf": cproto.NewCodec,
"application/proto-rpc": cprotorpc.NewCodec,
"application/octet-stream": craw.NewCodec,
}
)
const (
defaultContentType = "application/json"
)
type noopServer struct {
h Handler
opts Options
rsvc *registry.Service
handlers map[string]Handler
subscribers map[*subscriber][]broker.Subscriber
registered bool
started bool
exit chan chan error
wg *sync.WaitGroup
sync.RWMutex
}
// NewServer returns new noop server
func NewServer(opts ...Option) Server {
return &noopServer{opts: NewOptions(opts...)}
}
func (n *noopServer) newCodec(contentType string) (codec.NewCodec, error) {
if cf, ok := n.opts.Codecs[contentType]; ok {
return cf, nil
}
if cf, ok := DefaultCodecs[contentType]; ok {
return cf, nil
}
return nil, fmt.Errorf("Unsupported Content-Type: %s", contentType)
}
func (n *noopServer) Handle(handler Handler) error {
n.h = handler
return nil
}
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 {
return fmt.Errorf("invalid subscriber: no handler functions")
}
if err := ValidateSubscriber(sb); err != nil {
return err
}
n.Lock()
if _, ok = n.subscribers[sub]; ok {
n.Unlock()
return fmt.Errorf("subscriber %v already exists", sub)
}
n.subscribers[sub] = nil
n.Unlock()
return nil
}
func (n *noopServer) NewHandler(h interface{}, opts ...HandlerOption) Handler {
return newRpcHandler(h, opts...)
}
func (n *noopServer) NewSubscriber(topic string, sb interface{}, opts ...SubscriberOption) Subscriber {
return newSubscriber(topic, sb, opts...)
}
func (n *noopServer) Init(opts ...Option) error {
for _, o := range opts {
o(&n.opts)
}
if n.handlers == nil {
n.handlers = make(map[string]Handler)
}
if n.subscribers == nil {
n.subscribers = make(map[*subscriber][]broker.Subscriber)
}
if n.exit == nil {
n.exit = make(chan chan error)
}
return nil
}
func (n *noopServer) Options() Options {
return n.opts
}
func (n *noopServer) String() string {
return "noop"
}
func (n *noopServer) Register() error {
n.RLock()
rsvc := n.rsvc
config := n.opts
n.RUnlock()
// if service already filled, reuse it and return early
if rsvc != nil {
if err := DefaultRegisterFunc(rsvc, config); err != nil {
return err
}
return nil
}
var err error
var service *registry.Service
var cacheService bool
service, err = NewRegistryService(n)
if err != nil {
return err
}
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)
}
}
sort.Strings(handlerList)
var subscriberList []*subscriber
for e := range n.subscribers {
// Only advertise non internal subscribers
if !e.Options().Internal {
subscriberList = append(subscriberList, e)
}
}
sort.Slice(subscriberList, func(i, j int) bool {
return subscriberList[i].topic > subscriberList[j].topic
})
endpoints := make([]*registry.Endpoint, 0, len(handlerList)+len(subscriberList))
for _, h := range handlerList {
endpoints = append(endpoints, n.handlers[h].Endpoints()...)
}
for _, e := range subscriberList {
endpoints = append(endpoints, e.Endpoints()...)
}
n.RUnlock()
service.Nodes[0].Metadata["protocol"] = "noop"
service.Nodes[0].Metadata["transport"] = "noop"
service.Endpoints = endpoints
n.RLock()
registered := n.registered
n.RUnlock()
if !registered {
if config.Logger.V(logger.InfoLevel) {
config.Logger.Info("Registry [%s] Registering node: %s", config.Registry.String(), service.Nodes[0].Id)
}
}
// register the service
if err := DefaultRegisterFunc(service, config); err != nil {
return err
}
// already registered? don't need to register subscribers
if registered {
return nil
}
n.Lock()
defer n.Unlock()
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))
}
if sb.Options().Context != nil {
cx = sb.Options().Context
}
opts = append(opts, broker.SubscribeContext(cx), broker.SubscribeAutoAck(sb.Options().AutoAck))
if config.Logger.V(logger.InfoLevel) {
config.Logger.Info("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}
}
n.registered = true
if cacheService {
n.rsvc = service
}
return nil
}
func (n *noopServer) Deregister() error {
var err error
n.RLock()
config := n.opts
n.RUnlock()
service, err := NewRegistryService(n)
if err != nil {
return err
}
if config.Logger.V(logger.InfoLevel) {
config.Logger.Info("deregistering node: %s", service.Nodes[0].Id)
}
if err := DefaultDeregisterFunc(service, config); err != nil {
return err
}
n.Lock()
n.rsvc = nil
if !n.registered {
n.Unlock()
return nil
}
n.registered = false
cx := config.Context
wg := sync.WaitGroup{}
for sb, subs := range n.subscribers {
for _, sub := range subs {
if sb.Options().Context != nil {
cx = sb.Options().Context
}
wg.Add(1)
go func(s broker.Subscriber) {
defer wg.Done()
if config.Logger.V(logger.InfoLevel) {
config.Logger.Info("unsubscribing from topic: %s", s.Topic())
}
if err := s.Unsubscribe(cx); err != nil {
if config.Logger.V(logger.ErrorLevel) {
config.Logger.Error("unsubscribing from topic: %s err: %v", s.Topic(), err)
}
}
}(sub)
}
n.subscribers[sb] = nil
}
wg.Wait()
n.Unlock()
return nil
}
func (n *noopServer) Start() error {
n.RLock()
if n.started {
n.RUnlock()
return nil
}
config := n.Options()
n.RUnlock()
if config.Logger.V(logger.InfoLevel) {
config.Logger.Info("Server [noop] Listening on %s", config.Address)
}
n.Lock()
if len(config.Advertise) == 0 {
config.Advertise = config.Address
}
n.Unlock()
// only connect if we're subscribed
if len(n.subscribers) > 0 {
// connect to the broker
if err := config.Broker.Connect(config.Context); err != nil {
if config.Logger.V(logger.ErrorLevel) {
config.Logger.Error("Broker [%s] connect error: %v", config.Broker.String(), err)
}
return err
}
if config.Logger.V(logger.InfoLevel) {
config.Logger.Info("Broker [%s] Connected to %s", config.Broker.String(), config.Broker.Address())
}
}
// use RegisterCheck func before register
if err := config.RegisterCheck(config.Context); err != nil {
if config.Logger.V(logger.ErrorLevel) {
config.Logger.Error("Server %s-%s register check error: %s", config.Name, config.Id, err)
}
} else {
// announce self to the world
if err := n.Register(); err != nil {
if config.Logger.V(logger.ErrorLevel) {
config.Logger.Error("Server register error: %v", err)
}
}
}
go func() {
t := new(time.Ticker)
// only process if it exists
if config.RegisterInterval > time.Duration(0) {
// new ticker
t = time.NewTicker(config.RegisterInterval)
}
// return error chan
var ch chan error
Loop:
for {
select {
// register self on interval
case <-t.C:
n.RLock()
registered := n.registered
n.RUnlock()
rerr := config.RegisterCheck(config.Context)
if rerr != nil && registered {
if config.Logger.V(logger.ErrorLevel) {
config.Logger.Error("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.Error("Server %s-%s deregister error: %s", config.Name, config.Id, err)
}
}
} else if rerr != nil && !registered {
if config.Logger.V(logger.ErrorLevel) {
config.Logger.Error("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.Error("Server %s-%s register error: %s", config.Name, config.Id, err)
}
}
// wait for exit
case ch = <-n.exit:
break Loop
}
}
// deregister self
if err := n.Deregister(); err != nil {
if config.Logger.V(logger.ErrorLevel) {
config.Logger.Error("Server deregister error: ", err)
}
}
// wait for waitgroup
if n.wg != nil {
n.wg.Wait()
}
// close transport
ch <- nil
if config.Logger.V(logger.InfoLevel) {
config.Logger.Info("Broker [%s] Disconnected from %s", config.Broker.String(), config.Broker.Address())
}
// disconnect broker
if err := config.Broker.Disconnect(config.Context); err != nil {
if config.Logger.V(logger.ErrorLevel) {
config.Logger.Error("Broker [%s] disconnect error: %v", config.Broker.String(), err)
}
}
}()
// mark the server as started
n.Lock()
n.started = true
n.Unlock()
return nil
}
func (n *noopServer) Stop() error {
n.RLock()
if !n.started {
n.RUnlock()
return nil
}
n.RUnlock()
ch := make(chan error)
n.exit <- ch
err := <-ch
n.Lock()
n.rsvc = nil
n.started = false
n.Unlock()
return err
}
type noopcodec struct {
*bytes.Buffer
}
func (c noopcodec) Close() error {
return nil
}

View File

@@ -9,17 +9,18 @@ import (
"github.com/unistack-org/micro/v3/auth" "github.com/unistack-org/micro/v3/auth"
"github.com/unistack-org/micro/v3/broker" "github.com/unistack-org/micro/v3/broker"
"github.com/unistack-org/micro/v3/codec" "github.com/unistack-org/micro/v3/codec"
"github.com/unistack-org/micro/v3/debug/trace"
"github.com/unistack-org/micro/v3/logger" "github.com/unistack-org/micro/v3/logger"
"github.com/unistack-org/micro/v3/network/transport"
"github.com/unistack-org/micro/v3/registry" "github.com/unistack-org/micro/v3/registry"
"github.com/unistack-org/micro/v3/transport" "github.com/unistack-org/micro/v3/tracer"
) )
// Options server struct
type Options struct { type Options struct {
Codecs map[string]codec.NewCodec Codecs map[string]codec.NewCodec
Broker broker.Broker Broker broker.Broker
Registry registry.Registry Registry registry.Registry
Tracer trace.Tracer Tracer tracer.Tracer
Auth auth.Auth Auth auth.Auth
Logger logger.Logger Logger logger.Logger
Transport transport.Transport Transport transport.Transport
@@ -39,6 +40,10 @@ type Options struct {
RegisterTTL time.Duration RegisterTTL time.Duration
// The interval on which to register // The interval on which to register
RegisterInterval time.Duration RegisterInterval time.Duration
// RegisterAttempts specify how many times try to register
RegisterAttempts int
// DeegisterAttempts specify how many times try to deregister
DeregisterAttempts int
// The router for requests // The router for requests
Router Router Router Router
@@ -46,47 +51,42 @@ type Options struct {
// TLSConfig specifies tls.Config for secure serving // TLSConfig specifies tls.Config for secure serving
TLSConfig *tls.Config TLSConfig *tls.Config
Wait *sync.WaitGroup
// Other options for implementations of the interface // Other options for implementations of the interface
// can be stored in a context // can be stored in a context
Context context.Context Context context.Context
} }
func newOptions(opt ...Option) Options { // NewOptions returns new options struct with default or passed values
opts := Options{ func NewOptions(opts ...Option) Options {
options := Options{
Auth: auth.DefaultAuth,
Codecs: make(map[string]codec.NewCodec), Codecs: make(map[string]codec.NewCodec),
Context: context.Background(),
Metadata: map[string]string{}, Metadata: map[string]string{},
RegisterInterval: DefaultRegisterInterval, RegisterInterval: DefaultRegisterInterval,
RegisterTTL: DefaultRegisterTTL, RegisterTTL: DefaultRegisterTTL,
RegisterCheck: DefaultRegisterCheck,
Logger: logger.DefaultLogger,
Tracer: tracer.DefaultTracer,
Broker: broker.DefaultBroker,
Registry: registry.DefaultRegistry,
Transport: transport.DefaultTransport,
Address: DefaultAddress,
Name: DefaultName,
Version: DefaultVersion,
Id: DefaultId,
Namespace: DefaultNamespace,
} }
for _, o := range opt { for _, o := range opts {
o(&opts) o(&options)
} }
if opts.RegisterCheck == nil { return options
opts.RegisterCheck = DefaultRegisterCheck
}
if len(opts.Address) == 0 {
opts.Address = DefaultAddress
}
if len(opts.Name) == 0 {
opts.Name = DefaultName
}
if len(opts.Id) == 0 {
opts.Id = DefaultId
}
if len(opts.Version) == 0 {
opts.Version = DefaultVersion
}
return opts
} }
// Server name // Name sets the server name option
func Name(n string) Option { func Name(n string) Option {
return func(o *Options) { return func(o *Options) {
o.Name = n o.Name = n
@@ -100,14 +100,14 @@ func Namespace(n string) Option {
} }
} }
// Logger // Logger sets the logger option
func Logger(l logger.Logger) Option { func Logger(l logger.Logger) Option {
return func(o *Options) { return func(o *Options) {
o.Logger = l o.Logger = l
} }
} }
// Unique server id // Id unique server id
func Id(id string) Option { func Id(id string) Option {
return func(o *Options) { return func(o *Options) {
o.Id = id o.Id = id
@@ -128,7 +128,7 @@ func Address(a string) Option {
} }
} }
// The address to advertise for discovery - host:port // Advertise the address to advertise for discovery - host:port
func Advertise(a string) Option { func Advertise(a string) Option {
return func(o *Options) { return func(o *Options) {
o.Advertise = a o.Advertise = a
@@ -166,7 +166,7 @@ func Registry(r registry.Registry) Option {
} }
// Tracer mechanism for distributed tracking // Tracer mechanism for distributed tracking
func Tracer(t trace.Tracer) Option { func Tracer(t tracer.Tracer) Option {
return func(o *Options) { return func(o *Options) {
o.Tracer = t o.Tracer = t
} }
@@ -200,14 +200,14 @@ func RegisterCheck(fn func(context.Context) error) Option {
} }
} }
// Register the service with a TTL // RegisterTTL registers service with a TTL
func RegisterTTL(t time.Duration) Option { func RegisterTTL(t time.Duration) Option {
return func(o *Options) { return func(o *Options) {
o.RegisterTTL = t o.RegisterTTL = t
} }
} }
// Register the service with at interval // RegisterInterval registers service with at interval
func RegisterInterval(t time.Duration) Option { func RegisterInterval(t time.Duration) Option {
return func(o *Options) { return func(o *Options) {
o.RegisterInterval = t o.RegisterInterval = t
@@ -244,26 +244,120 @@ func WithRouter(r Router) Option {
// wait against it on stop. // wait against it on stop.
func Wait(wg *sync.WaitGroup) Option { func Wait(wg *sync.WaitGroup) Option {
return func(o *Options) { return func(o *Options) {
if o.Context == nil {
o.Context = context.Background()
}
if wg == nil { if wg == nil {
wg = new(sync.WaitGroup) wg = new(sync.WaitGroup)
} }
o.Context = context.WithValue(o.Context, "wait", wg) o.Wait = wg
} }
} }
// Adds a handler Wrapper to a list of options passed into the server // WrapHandler adds a handler Wrapper to a list of options passed into the server
func WrapHandler(w HandlerWrapper) Option { func WrapHandler(w HandlerWrapper) Option {
return func(o *Options) { return func(o *Options) {
o.HdlrWrappers = append(o.HdlrWrappers, w) o.HdlrWrappers = append(o.HdlrWrappers, w)
} }
} }
// Adds a subscriber Wrapper to a list of options passed into the server // WrapSubscriber adds a subscriber Wrapper to a list of options passed into the server
func WrapSubscriber(w SubscriberWrapper) Option { func WrapSubscriber(w SubscriberWrapper) Option {
return func(o *Options) { return func(o *Options) {
o.SubWrappers = append(o.SubWrappers, w) o.SubWrappers = append(o.SubWrappers, w)
} }
} }
// HandlerOption func
type HandlerOption func(*HandlerOptions)
// HandlerOptions struct
type HandlerOptions struct {
Internal bool
Metadata map[string]map[string]string
Context context.Context
}
// NewHandlerOptions creates new HandlerOptions
func NewHandlerOptions(opts ...HandlerOption) HandlerOptions {
options := HandlerOptions{
Context: context.Background(),
}
for _, o := range opts {
o(&options)
}
return options
}
// SubscriberOption func
type SubscriberOption func(*SubscriberOptions)
// SubscriberOptions struct
type SubscriberOptions struct {
// AutoAck defaults to true. When a handler returns
// with a nil error the message is acked.
AutoAck bool
Queue string
Internal bool
Context context.Context
}
// NewSubscriberOptions create new SubscriberOptions
func NewSubscriberOptions(opts ...SubscriberOption) SubscriberOptions {
options := SubscriberOptions{
AutoAck: true,
Context: context.Background(),
}
for _, o := range opts {
o(&options)
}
return options
}
// EndpointMetadata is a Handler option that allows metadata to be added to
// individual endpoints.
func EndpointMetadata(name string, md map[string]string) HandlerOption {
return func(o *HandlerOptions) {
o.Metadata[name] = md
}
}
// 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 {
return func(o *SubscriberOptions) {
o.AutoAck = false
}
}
// SubscriberQueue sets the shared queue name distributed messages across subscribers
func SubscriberQueue(n string) SubscriberOption {
return func(o *SubscriberOptions) {
o.Queue = n
}
}
// SubscriberContext set context options to allow broker SubscriberOption passed
func SubscriberContext(ctx context.Context) SubscriberOption {
return func(o *SubscriberOptions) {
o.Context = ctx
}
}

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