Compare commits

..

29 Commits

Author SHA1 Message Date
c320c23913 metadata: minor fixup for NewXXXContext functions
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-03-01 13:00:53 +03:00
Renovate Bot
ae848ba8bb fix(deps): update golang.org/x/net commit hash to e18ecbb 2021-02-26 19:46:14 +00:00
Renovate Bot
8e264cbb3e fix(deps): update golang.org/x/net commit hash to 39120d0 2021-02-26 12:05:10 +00:00
Renovate Bot
54e523ab3f fix(deps): update golang.org/x/net commit hash to 3d97a24 2021-02-26 08:40:37 +00:00
09973af099 server: add error helper
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-02-22 00:52:18 +03:00
3247da3dd0 metadata: add Pairs helper func
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-02-22 00:08:05 +03:00
b505455f7c run go mod tidy in renovate update
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-02-21 23:55:19 +03:00
293949f081 metadata: add Append func to Incoming/Outgoing context
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-02-21 23:54:59 +03:00
8d7e442b3a server: add SubscriberBodyOnly option
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-02-20 18:12:13 +03:00
renovate[bot]
f7b5211af3 fix(deps): update golang.org/x/net commit hash to 5f55cee (#20)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-02-20 11:40:35 +03:00
7eb6d030dc meter: fix internal labels sorting
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-02-18 15:57:42 +03:00
47e75c31c7 meter: export labels len method
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-02-18 14:41:51 +03:00
20ff5eed22 meter: initial wrapper import (#19)
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-02-18 14:35:10 +03:00
d23ca8db73 Merge pull request #18 from unistack-org/flow
flow: initial tests
2021-02-18 12:49:32 +03:00
4dd28ac720 go 1.15 => 1.16
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-02-18 12:49:02 +03:00
240b6016df flow: add initial flow dag
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-02-18 12:44:37 +03:00
cf2aa827e4 update to go 1.16
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-02-18 12:44:18 +03:00
5596345382 util/rand: replace all non crypto rand stuff with own rand package
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-02-18 12:44:18 +03:00
67748a2132 util/reflect: import own path based interface lookup
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-02-14 23:33:01 +03:00
c2333a9f35 gh actions not fail on lint errors
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-02-14 16:26:40 +03:00
4ec4c277b7 lint: fix all major issues
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-02-14 16:16:01 +03:00
a11dd00174 profiler: fix import
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-02-14 14:25:04 +03:00
cc7ebedf22 debug/profile: move to profiler interface
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-02-14 14:02:51 +03:00
e5bf1448f4 lint fixes
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-02-14 11:28:50 +03:00
f182bba6ff debug/log: remove stale files
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-02-13 20:01:57 +03:00
1f8810599b go.mod cleanup
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-02-13 15:37:33 +03:00
82248eb3b0 many lint fixes and optimizations (#17)
* util/kubernetes: drop stale files
* debug/log/kubernetes: drop stale files
* util/scope: remove stale files
* util/mdns: drop stale files
* lint fixes

Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-02-13 15:35:56 +03:00
abb9937787 fix lint issues (#16)
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-02-13 01:46:16 +03:00
fd5ed64729 metadata: fix nil metadata from FromIncoming/FromOutgoing context
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-02-12 17:10:35 +03:00
157 changed files with 1564 additions and 6621 deletions

View File

@@ -2,6 +2,7 @@
"extends": [
"config:base"
],
"postUpdateOptions": ["gomodTidy"],
"packageRules": [
{
"matchUpdateTypes": ["minor", "patch", "pin", "digest"],

View File

@@ -11,7 +11,7 @@ jobs:
- name: setup
uses: actions/setup-go@v2
with:
go-version: 1.15
go-version: 1.16
- name: cache
uses: actions/cache@v2
with:

View File

@@ -11,11 +11,11 @@ jobs:
- name: setup
uses: actions/setup-go@v2
with:
go-version: 1.15
go-version: 1.16
- name: cache
uses: actions/cache@v2
with:
path: ~/go/pkg/mod
path: ~/go/pkg
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: ${{ runner.os }}-go-
- name: sdk checkout

View File

@@ -1,8 +1,6 @@
run:
deadline: 5m
modules-download-mode: readonly
skip-dirs:
- util/mdns.new
skip-files:
- ".*\\.pb\\.go$"
- ".*\\.pb\\.micro\\.go$"
@@ -30,11 +28,3 @@ linters:
- interfacer
- typecheck
- dupl
output:
format: colored-line-number
# print lines of code with issue, default is true
print-issued-lines: true
# print linter name in the end of issue text, default is true
print-linter-name: true
# make issues output unique by line, default is true
uniq-by-line: true

View File

@@ -10,6 +10,7 @@ import (
"github.com/unistack-org/micro/v3/server"
)
// Api interface
type Api interface {
// Initialise options
Init(...Option) error
@@ -23,23 +24,25 @@ type Api interface {
String() string
}
// Options holds the options
type Options struct{}
// Option func signature
type Option func(*Options) error
// Endpoint is a mapping between an RPC method and HTTP endpoint
type Endpoint struct {
// RPC Method e.g. Greeter.Hello
// Name Greeter.Hello
Name string
// Description e.g what's this endpoint for
Description string
// API Handler e.g rpc, proxy
// Handler e.g rpc, proxy
Handler string
// HTTP Host e.g example.com
// Host e.g example.com
Host []string
// HTTP Methods e.g GET, POST
// Method e.g GET, POST
Method []string
// HTTP Path e.g /greeter. Expect POSIX regex
// Path e.g /greeter. Expect POSIX regex
Path []string
// Body destination
// "*" or "" - top level message value
@@ -53,28 +56,12 @@ type Endpoint struct {
type Service struct {
// Name of service
Name string
// The endpoint for this service
// Endpoint for this service
Endpoint *Endpoint
// Versions of this service
// Services that provides service
Services []*register.Service
}
func strip(s string) string {
return strings.TrimSpace(s)
}
func slice(s string) []string {
var sl []string
for _, p := range strings.Split(s, ",") {
if str := strip(p); len(str) > 0 {
sl = append(sl, strip(p))
}
}
return sl
}
// Encode encodes an endpoint to endpoint metadata
func Encode(e *Endpoint) map[string]string {
if e == nil {

View File

@@ -5,6 +5,7 @@ import (
"testing"
)
//nolint:gocyclo
func TestEncoding(t *testing.T) {
testData := []*Endpoint{
nil,

View File

@@ -1,121 +0,0 @@
// Package api provides an http-rpc handler which provides the entire http request over rpc
package api
import (
"net/http"
goapi "github.com/unistack-org/micro/v3/api"
"github.com/unistack-org/micro/v3/api/handler"
api "github.com/unistack-org/micro/v3/api/proto"
"github.com/unistack-org/micro/v3/client"
"github.com/unistack-org/micro/v3/errors"
"github.com/unistack-org/micro/v3/util/ctx"
"github.com/unistack-org/micro/v3/util/router"
)
type apiHandler struct {
opts handler.Options
s *goapi.Service
}
const (
Handler = "api"
)
// API handler is the default handler which takes api.Request and returns api.Response
func (a *apiHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
bsize := handler.DefaultMaxRecvSize
if a.opts.MaxRecvSize > 0 {
bsize = a.opts.MaxRecvSize
}
r.Body = http.MaxBytesReader(w, r.Body, bsize)
request, err := requestToProto(r)
if err != nil {
er := errors.InternalServerError("go.micro.api", err.Error())
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(500)
w.Write([]byte(er.Error()))
return
}
var service *goapi.Service
if a.s != nil {
// we were given the service
service = a.s
} else if a.opts.Router != nil {
// try get service from router
s, err := a.opts.Router.Route(r)
if err != nil {
er := errors.InternalServerError("go.micro.api", err.Error())
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(500)
w.Write([]byte(er.Error()))
return
}
service = s
} else {
// we have no way of routing the request
er := errors.InternalServerError("go.micro.api", "no route found")
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(500)
w.Write([]byte(er.Error()))
return
}
// create request and response
c := a.opts.Client
req := c.NewRequest(service.Name, service.Endpoint.Name, request)
rsp := &api.Response{}
// create the context from headers
cx := ctx.FromRequest(r)
if err := c.Call(cx, req, rsp, client.WithRouter(router.New(service.Services))); err != nil {
w.Header().Set("Content-Type", "application/json")
ce := errors.Parse(err.Error())
switch ce.Code {
case 0:
w.WriteHeader(500)
default:
w.WriteHeader(int(ce.Code))
}
w.Write([]byte(ce.Error()))
return
} else if rsp.StatusCode == 0 {
rsp.StatusCode = http.StatusOK
}
for _, header := range rsp.GetHeader() {
for _, val := range header.Values {
w.Header().Add(header.Key, val)
}
}
if len(w.Header().Get("Content-Type")) == 0 {
w.Header().Set("Content-Type", "application/json")
}
w.WriteHeader(int(rsp.StatusCode))
w.Write([]byte(rsp.Body))
}
func (a *apiHandler) String() string {
return "api"
}
func NewHandler(opts ...handler.Option) handler.Handler {
options := handler.NewOptions(opts...)
return &apiHandler{
opts: options,
}
}
func WithService(s *goapi.Service, opts ...handler.Option) handler.Handler {
options := handler.NewOptions(opts...)
return &apiHandler{
opts: options,
s: s,
}
}

View File

@@ -1,109 +0,0 @@
package api
import (
"fmt"
"mime"
"net"
"net/http"
"strings"
"github.com/oxtoacart/bpool"
api "github.com/unistack-org/micro/v3/api/proto"
)
var (
// need to calculate later to specify useful defaults
bufferPool = bpool.NewSizedBufferPool(1024, 8)
)
func requestToProto(r *http.Request) (*api.Request, error) {
if err := r.ParseForm(); err != nil {
return nil, fmt.Errorf("Error parsing form: %v", err)
}
req := &api.Request{
Path: r.URL.Path,
Method: r.Method,
Header: make(map[string]*api.Pair),
Get: make(map[string]*api.Pair),
Post: make(map[string]*api.Pair),
Url: r.URL.String(),
}
ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
if err != nil {
ct = "text/plain; charset=UTF-8" //default CT is text/plain
r.Header.Set("Content-Type", ct)
}
//set the body:
if r.Body != nil {
switch ct {
case "application/x-www-form-urlencoded":
// expect form vals in Post data
default:
buf := bufferPool.Get()
defer bufferPool.Put(buf)
if _, err = buf.ReadFrom(r.Body); err != nil {
return nil, err
}
req.Body = buf.String()
}
}
// Set X-Forwarded-For if it does not exist
if ip, _, err := net.SplitHostPort(r.RemoteAddr); err == nil {
if prior, ok := r.Header["X-Forwarded-For"]; ok {
ip = strings.Join(prior, ", ") + ", " + ip
}
// Set the header
req.Header["X-Forwarded-For"] = &api.Pair{
Key: "X-Forwarded-For",
Values: []string{ip},
}
}
// Host is stripped from net/http Headers so let's add it
req.Header["Host"] = &api.Pair{
Key: "Host",
Values: []string{r.Host},
}
// Get data
for key, vals := range r.URL.Query() {
header, ok := req.Get[key]
if !ok {
header = &api.Pair{
Key: key,
}
req.Get[key] = header
}
header.Values = vals
}
// Post data
for key, vals := range r.PostForm {
header, ok := req.Post[key]
if !ok {
header = &api.Pair{
Key: key,
}
req.Post[key] = header
}
header.Values = vals
}
for key, vals := range r.Header {
header, ok := req.Header[key]
if !ok {
header = &api.Pair{
Key: key,
}
req.Header[key] = header
}
header.Values = vals
}
return req, nil
}

View File

@@ -1,46 +0,0 @@
package api
import (
"net/http"
"net/url"
"testing"
)
func TestRequestToProto(t *testing.T) {
testData := []*http.Request{
{
Method: "GET",
Header: http.Header{
"Header": []string{"test"},
},
URL: &url.URL{
Scheme: "http",
Host: "localhost",
Path: "/foo/bar",
RawQuery: "param1=value1",
},
},
}
for _, d := range testData {
p, err := requestToProto(d)
if err != nil {
t.Fatal(err)
}
if p.Path != d.URL.Path {
t.Fatalf("Expected path %s got %s", d.URL.Path, p.Path)
}
if p.Method != d.Method {
t.Fatalf("Expected method %s got %s", d.Method, p.Method)
}
for k, v := range d.Header {
if val, ok := p.Header[k]; !ok {
t.Fatalf("Expected header %s", k)
} else {
if val.Values[0] != v[0] {
t.Fatalf("Expected val %s, got %s", val.Values[0], v[0])
}
}
}
}
}

View File

@@ -1,141 +0,0 @@
// Package event provides a handler which publishes an event
package event
import (
"encoding/json"
"fmt"
"net/http"
"path"
"regexp"
"strings"
"time"
"github.com/google/uuid"
"github.com/oxtoacart/bpool"
"github.com/unistack-org/micro/v3/api/handler"
proto "github.com/unistack-org/micro/v3/api/proto"
"github.com/unistack-org/micro/v3/util/ctx"
)
var (
bufferPool = bpool.NewSizedBufferPool(1024, 8)
)
type event struct {
opts handler.Options
}
var (
Handler = "event"
versionRe = regexp.MustCompilePOSIX("^v[0-9]+$")
)
func eventName(parts []string) string {
return strings.Join(parts, ".")
}
func evRoute(ns, p string) (string, string) {
p = path.Clean(p)
p = strings.TrimPrefix(p, "/")
if len(p) == 0 {
return ns, "event"
}
parts := strings.Split(p, "/")
// no path
if len(parts) == 0 {
// topic: namespace
// action: event
return strings.Trim(ns, "."), "event"
}
// Treat /v[0-9]+ as versioning
// /v1/foo/bar => topic: v1.foo action: bar
if len(parts) >= 2 && versionRe.Match([]byte(parts[0])) {
topic := ns + "." + strings.Join(parts[:2], ".")
action := eventName(parts[1:])
return topic, action
}
// /foo => topic: ns.foo action: foo
// /foo/bar => topic: ns.foo action: bar
topic := ns + "." + strings.Join(parts[:1], ".")
action := eventName(parts[1:])
return topic, action
}
func (e *event) ServeHTTP(w http.ResponseWriter, r *http.Request) {
bsize := handler.DefaultMaxRecvSize
if e.opts.MaxRecvSize > 0 {
bsize = e.opts.MaxRecvSize
}
r.Body = http.MaxBytesReader(w, r.Body, bsize)
// request to topic:event
// create event
// publish to topic
topic, action := evRoute(e.opts.Namespace, r.URL.Path)
// create event
ev := &proto.Event{
Name: action,
// TODO: dedupe event
Id: fmt.Sprintf("%s-%s-%s", topic, action, uuid.New().String()),
Header: make(map[string]*proto.Pair),
Timestamp: time.Now().Unix(),
}
// set headers
for key, vals := range r.Header {
header, ok := ev.Header[key]
if !ok {
header = &proto.Pair{
Key: key,
}
ev.Header[key] = header
}
header.Values = vals
}
// set body
if r.Method == "GET" {
bytes, _ := json.Marshal(r.URL.Query())
ev.Data = string(bytes)
} else {
// Read body
buf := bufferPool.Get()
defer bufferPool.Put(buf)
if _, err := buf.ReadFrom(r.Body); err != nil {
http.Error(w, err.Error(), 500)
return
}
ev.Data = buf.String()
}
// get client
c := e.opts.Client
// create publication
p := c.NewMessage(topic, ev)
// publish event
if err := c.Publish(ctx.FromRequest(r), p); err != nil {
http.Error(w, err.Error(), 500)
return
}
}
func (e *event) String() string {
return "event"
}
func NewHandler(opts ...handler.Option) handler.Handler {
return &event{
opts: handler.NewOptions(opts...),
}
}

View File

@@ -1,105 +0,0 @@
// Package http is a http reverse proxy handler
package http
import (
"errors"
"fmt"
"math/rand"
"net/http"
"net/http/httputil"
"net/url"
"github.com/unistack-org/micro/v3/api"
"github.com/unistack-org/micro/v3/api/handler"
"github.com/unistack-org/micro/v3/register"
)
const (
Handler = "http"
)
type httpHandler struct {
options handler.Options
// set with different initializer
s *api.Service
}
func (h *httpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
service, err := h.getService(r)
if err != nil {
w.WriteHeader(500)
return
}
if len(service) == 0 {
w.WriteHeader(404)
return
}
rp, err := url.Parse(service)
if err != nil {
w.WriteHeader(500)
return
}
httputil.NewSingleHostReverseProxy(rp).ServeHTTP(w, r)
}
// getService returns the service for this request from the selector
func (h *httpHandler) getService(r *http.Request) (string, error) {
var service *api.Service
if h.s != nil {
// we were given the service
service = h.s
} else if h.options.Router != nil {
// try get service from router
s, err := h.options.Router.Route(r)
if err != nil {
return "", err
}
service = s
} else {
// we have no way of routing the request
return "", errors.New("no route found")
}
if len(service.Services) == 0 {
return "", errors.New("no route found")
}
// get the nodes for this service
nodes := make([]*register.Node, 0, len(service.Services))
for _, srv := range service.Services {
nodes = append(nodes, srv.Nodes...)
}
// select a random node
node := nodes[rand.Int()%len(nodes)]
return fmt.Sprintf("http://%s", node.Address), nil
}
func (h *httpHandler) String() string {
return "http"
}
// NewHandler returns a http proxy handler
func NewHandler(opts ...handler.Option) handler.Handler {
options := handler.NewOptions(opts...)
return &httpHandler{
options: options,
}
}
// WithService creates a handler with a service
func WithService(s *api.Service, opts ...handler.Option) handler.Handler {
options := handler.NewOptions(opts...)
return &httpHandler{
options: options,
s: s,
}
}

View File

@@ -1,129 +0,0 @@
// +build ignore
package http
import (
"net"
"net/http"
"net/http/httptest"
"testing"
"github.com/unistack-org/micro/v3/api/handler"
"github.com/unistack-org/micro/v3/api/resolver"
"github.com/unistack-org/micro/v3/api/resolver/vpath"
"github.com/unistack-org/micro/v3/api/router"
regRouter "github.com/unistack-org/micro/v3/api/router/register"
"github.com/unistack-org/micro/v3/register"
"github.com/unistack-org/micro/v3/register/memory"
)
func testHttp(t *testing.T, path, service, ns string) {
r := memory.NewRegister()
l, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
defer l.Close()
s := &register.Service{
Name: service,
Nodes: []*register.Node{
{
Id: service + "-1",
Address: l.Addr().String(),
},
},
}
r.Register(s)
defer r.Deregister(s)
// setup the test handler
m := http.NewServeMux()
m.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`you got served`))
})
// start http test serve
go http.Serve(l, m)
// create new request and writer
w := httptest.NewRecorder()
req, err := http.NewRequest("POST", path, nil)
if err != nil {
t.Fatal(err)
}
// initialise the handler
rt := regRouter.NewRouter(
router.WithHandler("http"),
router.WithRegister(r),
router.WithResolver(vpath.NewResolver(
resolver.WithServicePrefix(ns),
)),
)
p := NewHandler(handler.WithRouter(rt))
// execute the handler
p.ServeHTTP(w, req)
if w.Code != 200 {
t.Fatalf("Expected 200 response got %d %s", w.Code, w.Body.String())
}
if w.Body.String() != "you got served" {
t.Fatalf("Expected body: you got served. Got: %s", w.Body.String())
}
}
func TestHttpHandler(t *testing.T) {
testData := []struct {
path string
service string
namespace string
}{
{
"/test/foo",
"go.micro.api.test",
"go.micro.api",
},
{
"/test/foo/baz",
"go.micro.api.test",
"go.micro.api",
},
{
"/v1/foo",
"go.micro.api.v1.foo",
"go.micro.api",
},
{
"/v1/foo/bar",
"go.micro.api.v1.foo",
"go.micro.api",
},
{
"/v2/baz",
"go.micro.api.v2.baz",
"go.micro.api",
},
{
"/v2/baz/bar",
"go.micro.api.v2.baz",
"go.micro.api",
},
{
"/v2/baz/bar",
"v2.baz",
"",
},
}
for _, d := range testData {
t.Run(d.service, func(t *testing.T) {
testHttp(t, d.path, d.service, d.namespace)
})
}
}

View File

@@ -3,24 +3,34 @@ package handler
import (
"github.com/unistack-org/micro/v3/api/router"
"github.com/unistack-org/micro/v3/client"
"github.com/unistack-org/micro/v3/logger"
)
var (
// DefaultMaxRecvSize specifies max recv size for handler
DefaultMaxRecvSize int64 = 1024 * 1024 * 100 // 10Mb
)
// Options struct holds handler options
type Options struct {
MaxRecvSize int64
Namespace string
Router router.Router
Client client.Client
Logger logger.Logger
}
// Option func signature
type Option func(o *Options)
// NewOptions fills in the blanks
// NewOptions creates new options struct and fills it
func NewOptions(opts ...Option) Options {
var options Options
options := Options{
Client: client.DefaultClient,
Router: router.DefaultRouter,
Logger: logger.DefaultLogger,
MaxRecvSize: DefaultMaxRecvSize,
}
for _, o := range opts {
o(&options)
}
@@ -30,10 +40,6 @@ func NewOptions(opts ...Option) Options {
WithNamespace("go.micro.api")(&options)
}
if options.MaxRecvSize == 0 {
options.MaxRecvSize = DefaultMaxRecvSize
}
return options
}
@@ -51,6 +57,7 @@ func WithRouter(r router.Router) Option {
}
}
// WithClient specifies client to be used by the handler
func WithClient(c client.Client) Option {
return func(o *Options) {
o.Client = c

View File

@@ -1,182 +0,0 @@
// Package web contains the web handler including websocket support
package web
import (
"errors"
"fmt"
"io"
"math/rand"
"net"
"net/http"
"net/http/httputil"
"net/url"
"strings"
"github.com/unistack-org/micro/v3/api"
"github.com/unistack-org/micro/v3/api/handler"
"github.com/unistack-org/micro/v3/register"
)
const (
Handler = "web"
)
type webHandler struct {
opts handler.Options
s *api.Service
}
func (wh *webHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
service, err := wh.getService(r)
if err != nil {
w.WriteHeader(500)
return
}
if len(service) == 0 {
w.WriteHeader(404)
return
}
rp, err := url.Parse(service)
if err != nil {
w.WriteHeader(500)
return
}
if isWebSocket(r) {
wh.serveWebSocket(rp.Host, w, r)
return
}
httputil.NewSingleHostReverseProxy(rp).ServeHTTP(w, r)
}
// getService returns the service for this request from the selector
func (wh *webHandler) getService(r *http.Request) (string, error) {
var service *api.Service
if wh.s != nil {
// we were given the service
service = wh.s
} else if wh.opts.Router != nil {
// try get service from router
s, err := wh.opts.Router.Route(r)
if err != nil {
return "", err
}
service = s
} else {
// we have no way of routing the request
return "", errors.New("no route found")
}
// get the nodes
nodes := make([]*register.Node, 0, len(service.Services))
for _, srv := range service.Services {
nodes = append(nodes, srv.Nodes...)
}
if len(nodes) == 0 {
return "", errors.New("no route found")
}
// select a random node
node := nodes[rand.Int()%len(nodes)]
return fmt.Sprintf("http://%s", node.Address), nil
}
// serveWebSocket used to serve a web socket proxied connection
func (wh *webHandler) serveWebSocket(host string, w http.ResponseWriter, r *http.Request) {
req := new(http.Request)
*req = *r
if len(host) == 0 {
http.Error(w, "invalid host", 500)
return
}
// set x-forward-for
if clientIP, _, err := net.SplitHostPort(r.RemoteAddr); err == nil {
if ips, ok := req.Header["X-Forwarded-For"]; ok {
clientIP = strings.Join(ips, ", ") + ", " + clientIP
}
req.Header.Set("X-Forwarded-For", clientIP)
}
// connect to the backend host
conn, err := net.Dial("tcp", host)
if err != nil {
http.Error(w, err.Error(), 500)
return
}
// hijack the connection
hj, ok := w.(http.Hijacker)
if !ok {
http.Error(w, "failed to connect", 500)
return
}
nc, _, err := hj.Hijack()
if err != nil {
return
}
defer nc.Close()
defer conn.Close()
if err = req.Write(conn); err != nil {
return
}
errCh := make(chan error, 2)
cp := func(dst io.Writer, src io.Reader) {
_, err := io.Copy(dst, src)
errCh <- err
}
go cp(conn, nc)
go cp(nc, conn)
<-errCh
}
func isWebSocket(r *http.Request) bool {
contains := func(key, val string) bool {
vv := strings.Split(r.Header.Get(key), ",")
for _, v := range vv {
if val == strings.ToLower(strings.TrimSpace(v)) {
return true
}
}
return false
}
if contains("Connection", "upgrade") && contains("Upgrade", "websocket") {
return true
}
return false
}
func (wh *webHandler) String() string {
return "web"
}
func NewHandler(opts ...handler.Option) handler.Handler {
return &webHandler{
opts: handler.NewOptions(opts...),
}
}
func WithService(s *api.Service, opts ...handler.Option) handler.Handler {
options := handler.NewOptions(opts...)
return &webHandler{
opts: options,
s: s,
}
}

View File

@@ -1,28 +0,0 @@
package proto
type Message struct {
data []byte
}
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,511 +0,0 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.25.0
// protoc v3.6.1
// source: api/proto/api.proto
package go_api
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 Pair struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
Values []string `protobuf:"bytes,2,rep,name=values,proto3" json:"values,omitempty"`
}
func (x *Pair) Reset() {
*x = Pair{}
if protoimpl.UnsafeEnabled {
mi := &file_api_proto_api_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Pair) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Pair) ProtoMessage() {}
func (x *Pair) ProtoReflect() protoreflect.Message {
mi := &file_api_proto_api_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 Pair.ProtoReflect.Descriptor instead.
func (*Pair) Descriptor() ([]byte, []int) {
return file_api_proto_api_proto_rawDescGZIP(), []int{0}
}
func (x *Pair) GetKey() string {
if x != nil {
return x.Key
}
return ""
}
func (x *Pair) GetValues() []string {
if x != nil {
return x.Values
}
return nil
}
// A HTTP request as RPC
// Forward by the api handler
type Request struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Method string `protobuf:"bytes,1,opt,name=method,proto3" json:"method,omitempty"`
Path string `protobuf:"bytes,2,opt,name=path,proto3" json:"path,omitempty"`
Header map[string]*Pair `protobuf:"bytes,3,rep,name=header,proto3" json:"header,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
Get map[string]*Pair `protobuf:"bytes,4,rep,name=get,proto3" json:"get,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
Post map[string]*Pair `protobuf:"bytes,5,rep,name=post,proto3" json:"post,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
Body string `protobuf:"bytes,6,opt,name=body,proto3" json:"body,omitempty"` // raw request body; if not application/x-www-form-urlencoded
Url string `protobuf:"bytes,7,opt,name=url,proto3" json:"url,omitempty"`
}
func (x *Request) Reset() {
*x = Request{}
if protoimpl.UnsafeEnabled {
mi := &file_api_proto_api_proto_msgTypes[1]
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_api_proto_api_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 Request.ProtoReflect.Descriptor instead.
func (*Request) Descriptor() ([]byte, []int) {
return file_api_proto_api_proto_rawDescGZIP(), []int{1}
}
func (x *Request) GetMethod() string {
if x != nil {
return x.Method
}
return ""
}
func (x *Request) GetPath() string {
if x != nil {
return x.Path
}
return ""
}
func (x *Request) GetHeader() map[string]*Pair {
if x != nil {
return x.Header
}
return nil
}
func (x *Request) GetGet() map[string]*Pair {
if x != nil {
return x.Get
}
return nil
}
func (x *Request) GetPost() map[string]*Pair {
if x != nil {
return x.Post
}
return nil
}
func (x *Request) GetBody() string {
if x != nil {
return x.Body
}
return ""
}
func (x *Request) GetUrl() string {
if x != nil {
return x.Url
}
return ""
}
// A HTTP response as RPC
// Expected response for the api handler
type Response struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
StatusCode int32 `protobuf:"varint,1,opt,name=statusCode,proto3" json:"statusCode,omitempty"`
Header map[string]*Pair `protobuf:"bytes,2,rep,name=header,proto3" json:"header,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
Body string `protobuf:"bytes,3,opt,name=body,proto3" json:"body,omitempty"`
}
func (x *Response) Reset() {
*x = Response{}
if protoimpl.UnsafeEnabled {
mi := &file_api_proto_api_proto_msgTypes[2]
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_api_proto_api_proto_msgTypes[2]
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_api_proto_api_proto_rawDescGZIP(), []int{2}
}
func (x *Response) GetStatusCode() int32 {
if x != nil {
return x.StatusCode
}
return 0
}
func (x *Response) GetHeader() map[string]*Pair {
if x != nil {
return x.Header
}
return nil
}
func (x *Response) GetBody() string {
if x != nil {
return x.Body
}
return ""
}
// A HTTP event as RPC
// Forwarded by the event handler
type Event struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// e.g login
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
// uuid
Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"`
// unix timestamp of event
Timestamp int64 `protobuf:"varint,3,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
// event headers
Header map[string]*Pair `protobuf:"bytes,4,rep,name=header,proto3" json:"header,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
// the event data
Data string `protobuf:"bytes,5,opt,name=data,proto3" json:"data,omitempty"`
}
func (x *Event) Reset() {
*x = Event{}
if protoimpl.UnsafeEnabled {
mi := &file_api_proto_api_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Event) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Event) ProtoMessage() {}
func (x *Event) ProtoReflect() protoreflect.Message {
mi := &file_api_proto_api_proto_msgTypes[3]
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 Event.ProtoReflect.Descriptor instead.
func (*Event) Descriptor() ([]byte, []int) {
return file_api_proto_api_proto_rawDescGZIP(), []int{3}
}
func (x *Event) GetName() string {
if x != nil {
return x.Name
}
return ""
}
func (x *Event) GetId() string {
if x != nil {
return x.Id
}
return ""
}
func (x *Event) GetTimestamp() int64 {
if x != nil {
return x.Timestamp
}
return 0
}
func (x *Event) GetHeader() map[string]*Pair {
if x != nil {
return x.Header
}
return nil
}
func (x *Event) GetData() string {
if x != nil {
return x.Data
}
return ""
}
var File_api_proto_api_proto protoreflect.FileDescriptor
var file_api_proto_api_proto_rawDesc = []byte{
0x0a, 0x13, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x61, 0x70, 0x69, 0x2e,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, 0x67, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x22, 0x30, 0x0a,
0x04, 0x50, 0x61, 0x69, 0x72, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65,
0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x22,
0xc1, 0x03, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x6d,
0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6d, 0x65, 0x74,
0x68, 0x6f, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28,
0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x33, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65,
0x72, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x6f, 0x2e, 0x61, 0x70, 0x69,
0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x45,
0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x2a, 0x0a, 0x03,
0x67, 0x65, 0x74, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x67, 0x6f, 0x2e, 0x61,
0x70, 0x69, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x6e,
0x74, 0x72, 0x79, 0x52, 0x03, 0x67, 0x65, 0x74, 0x12, 0x2d, 0x0a, 0x04, 0x70, 0x6f, 0x73, 0x74,
0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x50, 0x6f, 0x73, 0x74, 0x45, 0x6e, 0x74, 0x72,
0x79, 0x52, 0x04, 0x70, 0x6f, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18,
0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x75,
0x72, 0x6c, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x1a, 0x47, 0x0a,
0x0b, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03,
0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x22,
0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e,
0x67, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x50, 0x61, 0x69, 0x72, 0x52, 0x05, 0x76, 0x61, 0x6c,
0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x44, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x45, 0x6e, 0x74,
0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
0x03, 0x6b, 0x65, 0x79, 0x12, 0x22, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x67, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x50, 0x61, 0x69,
0x72, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x45, 0x0a, 0x09,
0x50, 0x6f, 0x73, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x22, 0x0a, 0x05, 0x76,
0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x67, 0x6f, 0x2e,
0x61, 0x70, 0x69, 0x2e, 0x50, 0x61, 0x69, 0x72, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a,
0x02, 0x38, 0x01, 0x22, 0xbd, 0x01, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x12, 0x1e, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x43, 0x6f, 0x64, 0x65, 0x18, 0x01,
0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x43, 0x6f, 0x64, 0x65,
0x12, 0x34, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b,
0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06,
0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x03,
0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x1a, 0x47, 0x0a, 0x0b, 0x48, 0x65,
0x61, 0x64, 0x65, 0x72, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x22, 0x0a, 0x05, 0x76,
0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x67, 0x6f, 0x2e,
0x61, 0x70, 0x69, 0x2e, 0x50, 0x61, 0x69, 0x72, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a,
0x02, 0x38, 0x01, 0x22, 0xd9, 0x01, 0x0a, 0x05, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x12, 0x0a,
0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d,
0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69,
0x64, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x03,
0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12,
0x31, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32,
0x19, 0x2e, 0x67, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x48,
0x65, 0x61, 0x64, 0x65, 0x72, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x68, 0x65, 0x61, 0x64,
0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09,
0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x1a, 0x47, 0x0a, 0x0b, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72,
0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x22, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65,
0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x67, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e,
0x50, 0x61, 0x69, 0x72, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x62,
0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_api_proto_api_proto_rawDescOnce sync.Once
file_api_proto_api_proto_rawDescData = file_api_proto_api_proto_rawDesc
)
func file_api_proto_api_proto_rawDescGZIP() []byte {
file_api_proto_api_proto_rawDescOnce.Do(func() {
file_api_proto_api_proto_rawDescData = protoimpl.X.CompressGZIP(file_api_proto_api_proto_rawDescData)
})
return file_api_proto_api_proto_rawDescData
}
var file_api_proto_api_proto_msgTypes = make([]protoimpl.MessageInfo, 9)
var file_api_proto_api_proto_goTypes = []interface{}{
(*Pair)(nil), // 0: go.api.Pair
(*Request)(nil), // 1: go.api.Request
(*Response)(nil), // 2: go.api.Response
(*Event)(nil), // 3: go.api.Event
nil, // 4: go.api.Request.HeaderEntry
nil, // 5: go.api.Request.GetEntry
nil, // 6: go.api.Request.PostEntry
nil, // 7: go.api.Response.HeaderEntry
nil, // 8: go.api.Event.HeaderEntry
}
var file_api_proto_api_proto_depIdxs = []int32{
4, // 0: go.api.Request.header:type_name -> go.api.Request.HeaderEntry
5, // 1: go.api.Request.get:type_name -> go.api.Request.GetEntry
6, // 2: go.api.Request.post:type_name -> go.api.Request.PostEntry
7, // 3: go.api.Response.header:type_name -> go.api.Response.HeaderEntry
8, // 4: go.api.Event.header:type_name -> go.api.Event.HeaderEntry
0, // 5: go.api.Request.HeaderEntry.value:type_name -> go.api.Pair
0, // 6: go.api.Request.GetEntry.value:type_name -> go.api.Pair
0, // 7: go.api.Request.PostEntry.value:type_name -> go.api.Pair
0, // 8: go.api.Response.HeaderEntry.value:type_name -> go.api.Pair
0, // 9: go.api.Event.HeaderEntry.value:type_name -> go.api.Pair
10, // [10:10] is the sub-list for method output_type
10, // [10:10] is the sub-list for method input_type
10, // [10:10] is the sub-list for extension type_name
10, // [10:10] is the sub-list for extension extendee
0, // [0:10] is the sub-list for field type_name
}
func init() { file_api_proto_api_proto_init() }
func file_api_proto_api_proto_init() {
if File_api_proto_api_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_api_proto_api_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Pair); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_api_proto_api_proto_msgTypes[1].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_api_proto_api_proto_msgTypes[2].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
}
}
file_api_proto_api_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Event); 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_api_proto_api_proto_rawDesc,
NumEnums: 0,
NumMessages: 9,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_api_proto_api_proto_goTypes,
DependencyIndexes: file_api_proto_api_proto_depIdxs,
MessageInfos: file_api_proto_api_proto_msgTypes,
}.Build()
File_api_proto_api_proto = out.File
file_api_proto_api_proto_rawDesc = nil
file_api_proto_api_proto_goTypes = nil
file_api_proto_api_proto_depIdxs = nil
}

View File

@@ -1,21 +0,0 @@
// Code generated by protoc-gen-micro. DO NOT EDIT.
// source: api/proto/api.proto
package go_api
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,43 +0,0 @@
syntax = "proto3";
package go.api;
message Pair {
string key = 1;
repeated string values = 2;
}
// A HTTP request as RPC
// Forward by the api handler
message Request {
string method = 1;
string path = 2;
map<string, Pair> header = 3;
map<string, Pair> get = 4;
map<string, Pair> post = 5;
string body = 6; // raw request body; if not application/x-www-form-urlencoded
string url = 7;
}
// A HTTP response as RPC
// Expected response for the api handler
message Response {
int32 statusCode = 1;
map<string, Pair> header = 2;
string body = 3;
}
// A HTTP event as RPC
// Forwarded by the event handler
message Event {
// e.g login
string name = 1;
// uuid
string id = 2;
// unix timestamp of event
int64 timestamp = 3;
// event headers
map<string, Pair> header = 4;
// the event data
string data = 5;
}

View File

@@ -7,11 +7,12 @@ import (
"github.com/unistack-org/micro/v3/api/resolver"
)
type Resolver struct {
type hostResolver struct {
opts resolver.Options
}
func (r *Resolver) Resolve(req *http.Request, opts ...resolver.ResolveOption) (*resolver.Endpoint, error) {
// Resolve endpoint
func (r *hostResolver) Resolve(req *http.Request, opts ...resolver.ResolveOption) (*resolver.Endpoint, error) {
// parse options
options := resolver.NewResolveOptions(opts...)
@@ -24,10 +25,11 @@ func (r *Resolver) Resolve(req *http.Request, opts ...resolver.ResolveOption) (*
}, nil
}
func (r *Resolver) String() string {
func (r *hostResolver) String() string {
return "host"
}
// NewResolver creates new host api resolver
func NewResolver(opts ...resolver.Option) resolver.Resolver {
return &Resolver{opts: resolver.NewOptions(opts...)}
return &hostResolver{opts: resolver.NewOptions(opts...)}
}

View File

@@ -8,10 +8,12 @@ import (
"github.com/unistack-org/micro/v3/api/resolver"
)
// Resolver the path resolver
type Resolver struct {
opts resolver.Options
}
// Resolve resolves endpoint
func (r *Resolver) Resolve(req *http.Request, opts ...resolver.ResolveOption) (*resolver.Endpoint, error) {
// parse options
options := resolver.NewResolveOptions(opts...)
@@ -31,10 +33,12 @@ func (r *Resolver) Resolve(req *http.Request, opts ...resolver.ResolveOption) (*
}, nil
}
// String retruns the string representation
func (r *Resolver) String() string {
return "path"
}
// NewResolver returns new path resolver
func NewResolver(opts ...resolver.Option) resolver.Resolver {
return &Resolver{opts: resolver.NewOptions(opts...)}
}

View File

@@ -12,17 +12,19 @@ import (
"golang.org/x/net/publicsuffix"
)
// NewResolver creates new subdomain api resolver
func NewResolver(parent resolver.Resolver, opts ...resolver.Option) resolver.Resolver {
options := resolver.NewOptions(opts...)
return &Resolver{options, parent}
return &subdomainResolver{options, parent}
}
type Resolver struct {
type subdomainResolver struct {
opts resolver.Options
resolver.Resolver
}
func (r *Resolver) Resolve(req *http.Request, opts ...resolver.ResolveOption) (*resolver.Endpoint, error) {
// Resolve resolve endpoint based on subdomain
func (r *subdomainResolver) Resolve(req *http.Request, opts ...resolver.ResolveOption) (*resolver.Endpoint, error) {
if dom := r.Domain(req); len(dom) > 0 {
opts = append(opts, resolver.Domain(dom))
}
@@ -30,7 +32,8 @@ func (r *Resolver) Resolve(req *http.Request, opts ...resolver.ResolveOption) (*
return r.Resolver.Resolve(req, opts...)
}
func (r *Resolver) Domain(req *http.Request) string {
// Domain returns domain
func (r *subdomainResolver) Domain(req *http.Request) string {
// determine the host, e.g. foobar.m3o.app
host := req.URL.Hostname()
if len(host) == 0 {
@@ -82,6 +85,6 @@ func (r *Resolver) Domain(req *http.Request) string {
return strings.Join(comps, "-")
}
func (r *Resolver) String() string {
func (r *subdomainResolver) String() string {
return "subdomain"
}

View File

@@ -6,8 +6,6 @@ import (
"testing"
"github.com/unistack-org/micro/v3/api/resolver/vpath"
"github.com/stretchr/testify/assert"
)
func TestResolve(t *testing.T) {
@@ -62,9 +60,13 @@ func TestResolve(t *testing.T) {
t.Run(tc.Name, func(t *testing.T) {
r := NewResolver(vpath.NewResolver())
result, err := r.Resolve(&http.Request{URL: &url.URL{Host: tc.Host, Path: "foo/bar"}})
assert.Nil(t, err, "Expecter err to be nil")
if err != nil {
t.Fatal(err)
}
if result != nil {
assert.Equal(t, tc.Result, result.Domain, "Expected %v but got %v", tc.Result, result.Domain)
if tc.Result != result.Domain {
t.Fatalf("Expected %v but got %v", tc.Result, result.Domain)
}
}
})
}

View File

@@ -10,11 +10,12 @@ import (
"github.com/unistack-org/micro/v3/api/resolver"
)
// NewResolver creates new vpath api resolver
func NewResolver(opts ...resolver.Option) resolver.Resolver {
return &Resolver{opts: resolver.NewOptions(opts...)}
return &vpathResolver{opts: resolver.NewOptions(opts...)}
}
type Resolver struct {
type vpathResolver struct {
opts resolver.Options
}
@@ -22,7 +23,8 @@ var (
re = regexp.MustCompile("^v[0-9]+$")
)
func (r *Resolver) Resolve(req *http.Request, opts ...resolver.ResolveOption) (*resolver.Endpoint, error) {
// Resolve endpoint
func (r *vpathResolver) Resolve(req *http.Request, opts ...resolver.ResolveOption) (*resolver.Endpoint, error) {
if req.URL.Path == "/" {
return nil, errors.New("unknown name")
}
@@ -60,12 +62,12 @@ func (r *Resolver) Resolve(req *http.Request, opts ...resolver.ResolveOption) (*
}, nil
}
func (r *Resolver) String() string {
return "path"
func (r *vpathResolver) String() string {
return "vpath"
}
// withPrefix transforms "foo" into "go.micro.api.foo"
func (r *Resolver) withPrefix(parts ...string) string {
func (r *vpathResolver) withPrefix(parts ...string) string {
p := r.opts.ServicePrefix
if len(p) > 0 {
parts = append([]string{p}, parts...)

View File

@@ -9,6 +9,7 @@ import (
"github.com/unistack-org/micro/v3/register"
)
// Options holds the options for api router
type Options struct {
Handler string
Register register.Register
@@ -17,8 +18,10 @@ type Options struct {
Context context.Context
}
// Option func signature
type Option func(o *Options)
// NewOptions returns options struct filled by opts
func NewOptions(opts ...Option) Options {
options := Options{
Context: context.Background(),

View File

@@ -7,6 +7,11 @@ import (
"github.com/unistack-org/micro/v3/api"
)
var (
// DefaultRouter contains default router implementation
DefaultRouter Router
)
// Router is used to determine an endpoint for a request
type Router interface {
// Returns options
@@ -23,6 +28,6 @@ type Router interface {
Deregister(ep *api.Endpoint) error
// Route returns an api.Service route
Route(r *http.Request) (*api.Service, error)
// String represenation of router
// String representation of router
String() string
}

View File

@@ -19,6 +19,7 @@ const (
)
var (
// DefaultAuth holds default auth implementation
DefaultAuth Auth = NewAuth()
// ErrInvalidToken is when the token provided is not valid
ErrInvalidToken = errors.New("invalid token provided")

View File

@@ -24,6 +24,7 @@ func NewOptions(opts ...Option) Options {
return options
}
// Options struct holds auth options
type Options struct {
Name string
// Issuer of the service's account

View File

@@ -9,6 +9,7 @@ import (
// VerifyAccess an account has access to a resource using the rules provided. If the account does not have
// access an error will be returned. If there are no rules provided which match the resource, an error
// will be returned
//nolint:gocyclo
func VerifyAccess(rules []*Rule, acc *Account, res *Resource) error {
// the rule is only to be applied if the type matches the resource or is catch-all (*)
validTypes := []string{"*", res.Type}

View File

@@ -3,14 +3,13 @@ package broker
import (
"context"
"errors"
"math/rand"
"sync"
"time"
"github.com/google/uuid"
"github.com/unistack-org/micro/v3/logger"
maddr "github.com/unistack-org/micro/v3/util/addr"
mnet "github.com/unistack-org/micro/v3/util/net"
"github.com/unistack-org/micro/v3/util/rand"
)
type memoryBroker struct {
@@ -59,7 +58,8 @@ func (m *memoryBroker) Connect(ctx context.Context) error {
if err != nil {
return err
}
i := rand.Intn(20000)
var rng rand.Rand
i := rng.Intn(20000)
// set addr with port
addr = mnet.HostPort(addr, 10000+i)
@@ -129,10 +129,8 @@ func (m *memoryBroker) Publish(ctx context.Context, topic string, msg *Message,
}
if eh != nil {
eh(p)
} else {
if m.opts.Logger.V(logger.ErrorLevel) {
m.opts.Logger.Error(m.opts.Context, err.Error())
}
} else if m.opts.Logger.V(logger.ErrorLevel) {
m.opts.Logger.Error(m.opts.Context, err.Error())
}
continue
}
@@ -237,9 +235,8 @@ func (m *memorySubscriber) Unsubscribe(ctx context.Context) error {
return nil
}
// NewBroker return new memory broker
func NewBroker(opts ...Option) Broker {
rand.Seed(time.Now().UnixNano())
return &memoryBroker{
opts: NewOptions(opts...),
Subscribers: make(map[string][]*memorySubscriber),

View File

@@ -12,6 +12,20 @@ import (
var (
// DefaultClient is the global default client
DefaultClient Client = NewClient()
// DefaultContentType is the default content-type if not specified
DefaultContentType = "application/json"
// DefaultBackoff is the default backoff function for retries
DefaultBackoff = exponentialBackoff
// DefaultRetry is the default check-for-retry function for retries
DefaultRetry = RetryNever
// DefaultRetries is the default number of times a request is tried
DefaultRetries = 0
// DefaultRequestTimeout is the default request timeout
DefaultRequestTimeout = time.Second * 5
// DefaultPoolSize sets the connection pool size
DefaultPoolSize = 100
// DefaultPoolTTL sets the connection pool ttl
DefaultPoolTTL = time.Minute
)
// Client is the interface used to make requests to services.
@@ -19,10 +33,10 @@ var (
// It also supports bidirectional streaming of requests.
type Client interface {
Name() string
Init(...Option) error
Init(opts ...Option) error
Options() Options
NewMessage(topic string, msg interface{}, opts ...MessageOption) Message
NewRequest(service, endpoint string, req interface{}, reqOpts ...RequestOption) Request
NewRequest(service string, endpoint string, req interface{}, opts ...RequestOption) Request
Call(ctx context.Context, req Request, rsp interface{}, opts ...CallOption) error
Stream(ctx context.Context, req Request, opts ...CallOption) (Stream, error)
Publish(ctx context.Context, msg Message, opts ...PublishOption) error
@@ -73,9 +87,9 @@ type Stream interface {
// The response read
Response() Response
// Send will encode and send a request
Send(interface{}) error
Send(msg interface{}) error
// Recv will decode and read a response
Recv(interface{}) error
Recv(msg interface{}) error
// Error returns the stream error
Error() error
// Close closes the stream
@@ -96,18 +110,3 @@ type MessageOption func(*MessageOptions)
// RequestOption used by NewRequest
type RequestOption func(*RequestOptions)
var (
// DefaultBackoff is the default backoff function for retries
DefaultBackoff = exponentialBackoff
// DefaultRetry is the default check-for-retry function for retries
DefaultRetry = RetryNever
// DefaultRetries is the default number of times a request is tried
DefaultRetries = 0
// DefaultRequestTimeout is the default request timeout
DefaultRequestTimeout = time.Second * 5
// DefaultPoolSize sets the connection pool size
DefaultPoolSize = 100
// DefaultPoolTTL sets the connection pool ttl
DefaultPoolTTL = time.Minute
)

View File

@@ -20,10 +20,6 @@ var (
}
)
const (
defaultContentType = "application/json"
)
type noopClient struct {
opts Options
}
@@ -179,7 +175,7 @@ func (n *noopClient) Call(ctx context.Context, req Request, rsp interface{}, opt
}
func (n *noopClient) NewRequest(service, endpoint string, req interface{}, opts ...RequestOption) Request {
return &noopRequest{}
return &noopRequest{service: service, endpoint: endpoint}
}
func (n *noopClient) NewMessage(topic string, msg interface{}, opts ...MessageOption) Message {

View File

@@ -64,13 +64,13 @@ type CallOptions struct {
Address []string
// Backoff func
Backoff BackoffFunc
// Transport Dial Timeout
// DialTimeout is the transport Dial Timeout
DialTimeout time.Duration
// Number of Call attempts
// Retries is the number of Call attempts
Retries int
// Check if retriable func
// Retry func to be used for retries
Retry RetryFunc
// Request/Response timeout
// RequestTimeout specifies request timeout
RequestTimeout time.Duration
// Router to use for this call
Router router.Router
@@ -78,15 +78,15 @@ type CallOptions struct {
Selector selector.Selector
// SelectOptions to use when selecting a route
SelectOptions []selector.SelectOption
// Stream timeout for the stream
// StreamTimeout timeout for the stream
StreamTimeout time.Duration
// Use the auth token as the authorization header
// AuthToken specifies the auth token as the authorization header
AuthToken bool
// Network to lookup the route within
Network string
// Middleware for low level call func
// CallWrappers is for low level call func
CallWrappers []CallWrapper
// Context is uded for non default options
// Context is used for non default options
Context context.Context
}
@@ -110,8 +110,7 @@ func NewPublishOptions(opts ...PublishOption) PublishOptions {
type PublishOptions struct {
// Exchange is the routing exchange for the message
Exchange string
// Other options for implementations of the interface
// can be stored in a context
// Context holds additional options
Context context.Context
}
@@ -140,10 +139,11 @@ func NewRequestOptions(opts ...RequestOption) RequestOptions {
// RequestOptions holds client request options
type RequestOptions struct {
// ContentType specify content-type of request
ContentType string
Stream bool
// Other options for implementations of the interface
// can be stored in a context
// Stream says that request is the streaming
Stream bool
// Context can hold other options
Context context.Context
}
@@ -151,7 +151,7 @@ type RequestOptions struct {
func NewOptions(opts ...Option) Options {
options := Options{
Context: context.Background(),
ContentType: "application/json",
ContentType: DefaultContentType,
Codecs: make(map[string]codec.Codec),
CallOptions: CallOptions{
Backoff: DefaultBackoff,
@@ -212,7 +212,7 @@ func Codec(contentType string, c codec.Codec) Option {
}
}
// Default content type of the client
// ContentType used by default if not specified
func ContentType(ct string) Option {
return func(o *Options) {
o.ContentType = ct
@@ -270,22 +270,21 @@ func Selector(s selector.Selector) Option {
}
}
// Adds a Wrapper to a list of options passed into the client
// Wrap adds a wrapper to the list of options passed into the client
func Wrap(w Wrapper) Option {
return func(o *Options) {
o.Wrappers = append(o.Wrappers, w)
}
}
// Adds a Wrapper to the list of CallFunc wrappers
// WrapCall adds a wrapper to the list of CallFunc wrappers
func WrapCall(cw ...CallWrapper) Option {
return func(o *Options) {
o.CallOptions.CallWrappers = append(o.CallOptions.CallWrappers, cw...)
}
}
// Backoff is used to set the backoff function used
// when retrying Calls
// Backoff is used to set the backoff function used when retrying Calls
func Backoff(fn BackoffFunc) Option {
return func(o *Options) {
o.CallOptions.Backoff = fn
@@ -307,7 +306,6 @@ func Lookup(l LookupFunc) Option {
}
// Retries sets the retry count when making the request.
// Should this be a Call Option?
func Retries(i int) Option {
return func(o *Options) {
o.CallOptions.Retries = i
@@ -322,7 +320,6 @@ func Retry(fn RetryFunc) Option {
}
// RequestTimeout is the request timeout.
// Should this be a Call Option?
func RequestTimeout(d time.Duration) Option {
return func(o *Options) {
o.CallOptions.RequestTimeout = d
@@ -336,15 +333,13 @@ func StreamTimeout(d time.Duration) Option {
}
}
// Transport dial timeout
// DialTimeout sets the dial timeout
func DialTimeout(d time.Duration) Option {
return func(o *Options) {
o.CallOptions.DialTimeout = d
}
}
// Call Options
// WithExchange sets the exchange to route a message through
func WithExchange(e string) PublishOption {
return func(o *PublishOptions) {
@@ -463,8 +458,6 @@ func WithMessageContentType(ct string) MessageOption {
}
}
// Request Options
// WithContentType specifies request content type
func WithContentType(ct string) RequestOption {
return func(o *RequestOptions) {

View File

@@ -8,8 +8,8 @@ import (
"github.com/unistack-org/micro/v3/metadata"
)
// Message types
const (
// Message types
Error MessageType = iota
Request
Response
@@ -25,11 +25,12 @@ var (
var (
// DefaultMaxMsgSize specifies how much data codec can handle
DefaultMaxMsgSize int = 1024 * 1024 * 4 // 4Mb
DefaultCodec Codec = NewCodec()
DefaultMaxMsgSize int = 1024 * 1024 * 4 // 4Mb
// DefaultCodec is the global default codec
DefaultCodec Codec = NewCodec()
)
// MessageType
// MessageType specifies message type for codec
type MessageType int
// Codec encodes/decodes various types of messages used within micro.

View File

@@ -3,7 +3,6 @@ package codec
import (
"encoding/json"
"io"
"io/ioutil"
)
type noopCodec struct {
@@ -20,7 +19,7 @@ func (c *noopCodec) ReadHeader(conn io.Reader, m *Message, t MessageType) error
func (c *noopCodec) ReadBody(conn io.Reader, b interface{}) error {
// read bytes
buf, err := ioutil.ReadAll(conn)
buf, err := io.ReadAll(conn)
if err != nil {
return err
}
@@ -30,12 +29,8 @@ func (c *noopCodec) ReadBody(conn io.Reader, b interface{}) error {
}
switch v := b.(type) {
case string:
v = string(buf)
case *string:
*v = string(buf)
case []byte:
v = buf
case *[]byte:
*v = buf
case *Frame:
@@ -112,15 +107,9 @@ func (c *noopCodec) Unmarshal(d []byte, v interface{}) error {
return nil
}
switch ve := v.(type) {
case string:
ve = string(d)
return nil
case *string:
*ve = string(d)
return nil
case []byte:
ve = d
return nil
case *[]byte:
*ve = d
return nil

34
codec/noop_test.go Normal file
View File

@@ -0,0 +1,34 @@
package codec
import (
"bytes"
"testing"
)
func TestNoopBytes(t *testing.T) {
req := []byte("test req")
rsp := make([]byte, len(req))
nc := NewCodec()
if err := nc.Unmarshal(req, &rsp); err != nil {
t.Fatal(err)
}
if !bytes.Equal(req, rsp) {
t.Fatalf("req not eq rsp: %s != %s", req, rsp)
}
}
func TestNoopString(t *testing.T) {
req := []byte("test req")
var rsp string
nc := NewCodec()
if err := nc.Unmarshal(req, &rsp); err != nil {
t.Fatal(err)
}
if !bytes.Equal(req, []byte(rsp)) {
t.Fatalf("req not eq rsp: %s != %s", req, rsp)
}
}

View File

@@ -45,6 +45,7 @@ func Meter(m meter.Meter) Option {
}
}
// NewOptions returns new options
func NewOptions(opts ...Option) Options {
options := Options{
Logger: logger.DefaultLogger,

View File

@@ -6,6 +6,7 @@ import (
type configKey struct{}
// FromContext returns store from context
func FromContext(ctx context.Context) (Config, bool) {
if ctx == nil {
return nil, false
@@ -14,6 +15,7 @@ func FromContext(ctx context.Context) (Config, bool) {
return c, ok
}
// NewContext put store in context
func NewContext(ctx context.Context, c Config) context.Context {
if ctx == nil {
ctx = context.Background()

View File

@@ -53,6 +53,7 @@ func (c *defaultConfig) Load(ctx context.Context) error {
return nil
}
//nolint:gocyclo
func (c *defaultConfig) fillValue(ctx context.Context, value reflect.Value, val string) error {
if !rutil.IsEmpty(value) {
return nil
@@ -107,7 +108,7 @@ func (c *defaultConfig) fillValue(ctx context.Context, value reflect.Value, val
if err != nil {
return err
}
value.Set(reflect.ValueOf(float64(v)))
value.Set(reflect.ValueOf(v))
case reflect.Int:
v, err := strconv.ParseInt(val, 10, 0)
if err != nil {
@@ -137,7 +138,7 @@ func (c *defaultConfig) fillValue(ctx context.Context, value reflect.Value, val
if err != nil {
return err
}
value.Set(reflect.ValueOf(int64(v)))
value.Set(reflect.ValueOf(v))
case reflect.Uint:
v, err := strconv.ParseUint(val, 10, 0)
if err != nil {
@@ -167,7 +168,7 @@ func (c *defaultConfig) fillValue(ctx context.Context, value reflect.Value, val
if err != nil {
return err
}
value.Set(reflect.ValueOf(uint64(v)))
value.Set(reflect.ValueOf(v))
}
return nil
}
@@ -255,6 +256,7 @@ func (c *defaultConfig) Name() string {
return c.opts.Name
}
// NewConfig returns new default config source
func NewConfig(opts ...Option) Config {
options := NewOptions(opts...)
if len(options.StructTag) == 0 {

View File

@@ -9,6 +9,7 @@ import (
"github.com/unistack-org/micro/v3/tracer"
)
// Options hold the config options
type Options struct {
Name string
AllowFail bool
@@ -32,8 +33,10 @@ type Options struct {
Context context.Context
}
// Option function signature
type Option func(o *Options)
// NewOptions new options struct with filed values
func NewOptions(opts ...Option) Options {
options := Options{
Logger: logger.DefaultLogger,
@@ -48,36 +51,42 @@ func NewOptions(opts ...Option) Options {
return options
}
// AllowFail allows config source to fail
func AllowFail(b bool) Option {
return func(o *Options) {
o.AllowFail = b
}
}
// BeforeLoad run funcs before config load
func BeforeLoad(fn ...func(context.Context, Config) error) Option {
return func(o *Options) {
o.BeforeLoad = fn
}
}
// AfterLoad run funcs after config load
func AfterLoad(fn ...func(context.Context, Config) error) Option {
return func(o *Options) {
o.AfterLoad = fn
}
}
// BeforeSave run funcs before save
func BeforeSave(fn ...func(context.Context, Config) error) Option {
return func(o *Options) {
o.BeforeSave = fn
}
}
// AfterSave run fncs after save
func AfterSave(fn ...func(context.Context, Config) error) Option {
return func(o *Options) {
o.AfterSave = fn
}
}
// Context pass context
func Context(ctx context.Context) Option {
return func(o *Options) {
o.Context = ctx
@@ -91,6 +100,7 @@ func Codec(c codec.Codec) Option {
}
}
// Logger sets the logger
func Logger(l logger.Logger) Option {
return func(o *Options) {
o.Logger = l
@@ -111,7 +121,7 @@ func Struct(v interface{}) Option {
}
}
// StructTag
// StructTag sets the struct tag that used for filling
func StructTag(name string) Option {
return func(o *Options) {
o.StructTag = name

View File

@@ -1,2 +0,0 @@
// Package debug provides interfaces for service debugging
package debug

View File

@@ -1,190 +0,0 @@
// Package kubernetes is a logger implementing (github.com/unistack-org/micro/v3/debug/log).Log
package kubernetes
import (
"bufio"
"encoding/json"
"fmt"
"os"
"sort"
"strconv"
"time"
"github.com/unistack-org/micro/v3/debug/log"
"github.com/unistack-org/micro/v3/metadata"
"github.com/unistack-org/micro/v3/util/kubernetes/client"
)
type klog struct {
client client.Client
log.Options
}
func (k *klog) podLogStream(podName string, stream *kubeStream) {
p := make(map[string]string)
p["follow"] = "true"
// get the logs for the pod
body, err := k.client.Log(&client.Resource{
Name: podName,
Kind: "pod",
}, client.LogParams(p))
if err != nil {
fmt.Fprintf(os.Stderr, "%v", err)
return
}
s := bufio.NewScanner(body)
defer body.Close()
for {
select {
case <-stream.stop:
return
default:
if s.Scan() {
record := k.parse(s.Text())
stream.stream <- record
} else {
// TODO: is there a blocking call
// rather than a sleep loop?
time.Sleep(time.Second)
}
}
}
}
func (k *klog) getMatchingPods() ([]string, error) {
r := &client.Resource{
Kind: "pod",
Value: new(client.PodList),
}
l := make(map[string]string)
l["name"] = client.Format(k.Options.Name)
// TODO: specify micro:service
// l["micro"] = "service"
if err := k.client.Get(r, client.GetLabels(l)); err != nil {
return nil, err
}
var matches []string
for _, p := range r.Value.(*client.PodList).Items {
// find labels that match the name
if p.Metadata.Labels["name"] == client.Format(k.Options.Name) {
matches = append(matches, p.Metadata.Name)
}
}
return matches, nil
}
func (k *klog) parse(line string) log.Record {
record := log.Record{}
if err := json.Unmarshal([]byte(line), &record); err != nil {
record.Timestamp = time.Now().UTC()
record.Message = line
record.Metadata = metadata.New(1)
}
record.Metadata["service"] = k.Options.Name
return record
}
func (k *klog) Read(options ...log.ReadOption) ([]log.Record, error) {
opts := &log.ReadOptions{}
for _, o := range options {
o(opts)
}
pods, err := k.getMatchingPods()
if err != nil {
return nil, err
}
var records []log.Record
for _, pod := range pods {
logParams := make(map[string]string)
if !opts.Since.Equal(time.Time{}) {
logParams["sinceSeconds"] = strconv.Itoa(int(time.Since(opts.Since).Seconds()))
}
if opts.Count != 0 {
logParams["tailLines"] = strconv.Itoa(opts.Count)
}
if opts.Stream {
logParams["follow"] = "true"
}
logs, err := k.client.Log(&client.Resource{
Name: pod,
Kind: "pod",
}, client.LogParams(logParams))
if err != nil {
return nil, err
}
defer logs.Close()
s := bufio.NewScanner(logs)
for s.Scan() {
record := k.parse(s.Text())
record.Metadata["pod"] = pod
records = append(records, record)
}
}
// sort the records
sort.Slice(records, func(i, j int) bool { return records[i].Timestamp.Before(records[j].Timestamp) })
return records, nil
}
func (k *klog) Write(l log.Record) error {
return write(l)
}
func (k *klog) Stream() (log.Stream, error) {
// find the matching pods
pods, err := k.getMatchingPods()
if err != nil {
return nil, err
}
stream := &kubeStream{
stream: make(chan log.Record),
stop: make(chan bool),
}
// stream from the individual pods
for _, pod := range pods {
go k.podLogStream(pod, stream)
}
return stream, nil
}
// NewLog returns a configured Kubernetes logger
func NewLog(opts ...log.Option) log.Log {
klog := &klog{}
for _, o := range opts {
o(&klog.Options)
}
if len(os.Getenv("KUBERNETES_SERVICE_HOST")) > 0 {
klog.client = client.NewClusterClient()
} else {
klog.client = client.NewLocalClient()
}
return klog
}

View File

@@ -1,71 +0,0 @@
package kubernetes
import (
"bytes"
"encoding/json"
"io"
"os"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/unistack-org/micro/v3/debug/log"
)
func TestKubernetes(t *testing.T) {
if len(os.Getenv("INTEGRATION_TESTS")) > 0 {
t.Skip()
}
k := NewLog(log.Name("micro-network"))
r, w, err := os.Pipe()
if err != nil {
t.Fatal(err)
}
s := os.Stderr
os.Stderr = w
meta := make(map[string]string)
write := log.Record{
Timestamp: time.Unix(0, 0).UTC(),
Message: "Test log entry",
Metadata: meta,
}
meta["foo"] = "bar"
k.Write(write)
b := &bytes.Buffer{}
w.Close()
io.Copy(b, r)
os.Stderr = s
var read log.Record
if err := json.Unmarshal(b.Bytes(), &read); err != nil {
t.Fatalf("json.Unmarshal failed: %s", err.Error())
}
assert.Equal(t, write, read, "Write was not equal")
records, err := k.Read()
assert.Nil(t, err, "Read should not error")
assert.NotNil(t, records, "Read should return records")
stream, err := k.Stream()
if err != nil {
t.Fatal(err)
}
records = nil
go stream.Stop()
for s := range stream.Chan() {
records = append(records, s)
}
assert.Equal(t, 0, len(records), "Stream should return nothing")
}

View File

@@ -1,44 +0,0 @@
package kubernetes
import (
"encoding/json"
"fmt"
"os"
"sync"
"github.com/unistack-org/micro/v3/debug/log"
)
func write(l log.Record) error {
m, err := json.Marshal(l)
if err == nil {
_, err := fmt.Fprintf(os.Stderr, "%s", m)
return err
}
return err
}
type kubeStream struct {
// the k8s log stream
stream chan log.Record
sync.Mutex
// the stop chan
stop chan bool
}
func (k *kubeStream) Chan() <-chan log.Record {
return k.stream
}
func (k *kubeStream) Stop() error {
k.Lock()
defer k.Unlock()
select {
case <-k.stop:
return nil
default:
close(k.stop)
close(k.stream)
}
return nil
}

View File

@@ -1,58 +0,0 @@
// Package log provides debug logging
package log
import (
"encoding/json"
"fmt"
"time"
"github.com/unistack-org/micro/v3/metadata"
)
var (
// Default buffer size if any
DefaultSize = 256
// Default formatter
DefaultFormat = TextFormat
)
// Log is debug log interface for reading and writing logs
type Log interface {
// Read reads log entries from the logger
Read(...ReadOption) ([]Record, error)
// Write writes records to log
Write(Record) error
// Stream log records
Stream() (Stream, error)
}
// Record is log record entry
type Record struct {
// Timestamp of logged event
Timestamp time.Time `json:"timestamp"`
// Metadata to enrich log record
Metadata metadata.Metadata `json:"metadata"`
// Value contains log entry
Message interface{} `json:"message"`
}
// Stream returns a log stream
type Stream interface {
Chan() <-chan Record
Stop() error
}
// Format is a function which formats the output
type FormatFunc func(Record) string
// TextFormat returns text format
func TextFormat(r Record) string {
t := r.Timestamp.Format("2006-01-02 15:04:05")
return fmt.Sprintf("%s %v", t, r.Message)
}
// JSONFormat is a json Format func
func JSONFormat(r Record) string {
b, _ := json.Marshal(r)
return string(b)
}

View File

@@ -1,115 +0,0 @@
// Package memory provides an in memory log buffer
package memory
import (
"fmt"
"github.com/unistack-org/micro/v3/debug/log"
"github.com/unistack-org/micro/v3/metadata"
"github.com/unistack-org/micro/v3/util/ring"
)
// memoryLog is default micro log
type memoryLog struct {
*ring.Buffer
}
// NewLog returns default Logger with
func NewLog(opts ...log.Option) log.Log {
// get default options
options := log.DefaultOptions()
// apply requested options
for _, o := range opts {
o(&options)
}
return &memoryLog{
Buffer: ring.New(options.Size),
}
}
// Write writes logs into logger
func (l *memoryLog) Write(r log.Record) error {
l.Buffer.Put(fmt.Sprint(r.Message))
return nil
}
// Read reads logs and returns them
func (l *memoryLog) Read(opts ...log.ReadOption) ([]log.Record, error) {
options := log.ReadOptions{}
// initialize the read options
for _, o := range opts {
o(&options)
}
var entries []*ring.Entry
// if Since options ha sbeen specified we honor it
if !options.Since.IsZero() {
entries = l.Buffer.Since(options.Since)
}
// only if we specified valid count constraint
// do we end up doing some serious if-else kung-fu
// if since constraint has been provided
// we return *count* number of logs since the given timestamp;
// otherwise we return last count number of logs
if options.Count > 0 {
switch len(entries) > 0 {
case true:
// if we request fewer logs than what since constraint gives us
if options.Count < len(entries) {
entries = entries[0:options.Count]
}
default:
entries = l.Buffer.Get(options.Count)
}
}
records := make([]log.Record, 0, len(entries))
for _, entry := range entries {
record := log.Record{
Timestamp: entry.Timestamp,
Message: entry.Value,
}
records = append(records, record)
}
return records, nil
}
// Stream returns channel for reading log records
// along with a stop channel, close it when done
func (l *memoryLog) Stream() (log.Stream, error) {
// get stream channel from ring buffer
stream, stop := l.Buffer.Stream()
// make a buffered channel
records := make(chan log.Record, 128)
// get last 10 records
last10 := l.Buffer.Get(10)
// stream the log records
go func() {
// first send last 10 records
for _, entry := range last10 {
records <- log.Record{
Timestamp: entry.Timestamp,
Message: entry.Value,
Metadata: metadata.New(0),
}
}
// now stream continuously
for entry := range stream {
records <- log.Record{
Timestamp: entry.Timestamp,
Message: entry.Value,
Metadata: metadata.New(0),
}
}
}()
return &logStream{
stream: records,
stop: stop,
}, nil
}

View File

@@ -1,32 +0,0 @@
package memory
import (
"reflect"
"testing"
"github.com/unistack-org/micro/v3/debug/log"
)
func TestLogger(t *testing.T) {
// set size to some value
size := 100
// override the global logger
lg := NewLog(log.Size(size))
// make sure we have the right size of the logger ring buffer
if lg.(*memoryLog).Size() != size {
t.Errorf("expected buffer size: %d, got: %d", size, lg.(*memoryLog).Size())
}
// Log some cruft
lg.Write(log.Record{Message: "foobar"})
lg.Write(log.Record{Message: "foo bar"})
// Check if the logs are stored in the logger ring buffer
expected := []string{"foobar", "foo bar"}
entries, _ := lg.Read(log.Count(len(expected)))
for i, entry := range entries {
if !reflect.DeepEqual(entry.Message, expected[i]) {
t.Errorf("expected %s, got %s", expected[i], entry.Message)
}
}
}

View File

@@ -1,24 +0,0 @@
package memory
import (
"github.com/unistack-org/micro/v3/debug/log"
)
type logStream struct {
stream <-chan log.Record
stop chan bool
}
func (l *logStream) Chan() <-chan log.Record {
return l.stream
}
func (l *logStream) Stop() error {
select {
case <-l.stop:
return nil
default:
close(l.stop)
}
return nil
}

View File

@@ -1,23 +0,0 @@
package noop
import (
"github.com/unistack-org/micro/v3/debug/log"
)
type noop struct{}
func (n *noop) Read(...log.ReadOption) ([]log.Record, error) {
return nil, nil
}
func (n *noop) Write(log.Record) error {
return nil
}
func (n *noop) Stream() (log.Stream, error) {
return nil, nil
}
func NewLog(opts ...log.Option) log.Log {
return new(noop)
}

View File

@@ -1,70 +0,0 @@
package log
import "time"
// Option used by the logger
type Option func(*Options)
// Options are logger options
type Options struct {
// Name of the log
Name string
// Size is the size of ring buffer
Size int
// Format specifies the output format
Format FormatFunc
}
// Name of the log
func Name(n string) Option {
return func(o *Options) {
o.Name = n
}
}
// Size sets the size of the ring buffer
func Size(s int) Option {
return func(o *Options) {
o.Size = s
}
}
func Format(f FormatFunc) Option {
return func(o *Options) {
o.Format = f
}
}
// DefaultOptions returns default options
func DefaultOptions() Options {
return Options{
Size: DefaultSize,
}
}
// ReadOptions for querying the logs
type ReadOptions struct {
// Since what time in past to return the logs
Since time.Time
// Count specifies number of logs to return
Count int
// Stream requests continuous log stream
Stream bool
}
// ReadOption used for reading the logs
type ReadOption func(*ReadOptions)
// Since sets the time since which to return the log records
func Since(s time.Time) ReadOption {
return func(o *ReadOptions) {
o.Since = s
}
}
// Count sets the number of log records to return
func Count(c int) ReadOption {
return func(o *ReadOptions) {
o.Count = c
}
}

View File

@@ -1,89 +0,0 @@
package stats
import (
"runtime"
"sync"
"time"
"github.com/unistack-org/micro/v3/debug/stats"
"github.com/unistack-org/micro/v3/util/ring"
)
type memoryStats struct {
// used to store past stats
buffer *ring.Buffer
sync.RWMutex
started int64
requests uint64
errors uint64
}
func (s *memoryStats) snapshot() *stats.Stat {
s.RLock()
defer s.RUnlock()
var mstat runtime.MemStats
runtime.ReadMemStats(&mstat)
now := time.Now().Unix()
return &stats.Stat{
Timestamp: now,
Started: s.started,
Uptime: now - s.started,
Memory: mstat.Alloc,
GC: mstat.PauseTotalNs,
Threads: uint64(runtime.NumGoroutine()),
Requests: s.requests,
Errors: s.errors,
}
}
func (s *memoryStats) Read() ([]*stats.Stat, error) {
buf := s.buffer.Get(s.buffer.Size())
var buffer []*stats.Stat
// get a value from the buffer if it exists
for _, b := range buf {
stat, ok := b.Value.(*stats.Stat)
if !ok {
continue
}
buffer = append(buffer, stat)
}
// get a snapshot
buffer = append(buffer, s.snapshot())
return buffer, nil
}
func (s *memoryStats) Write(stat *stats.Stat) error {
s.buffer.Put(stat)
return nil
}
func (s *memoryStats) Record(err error) error {
s.Lock()
defer s.Unlock()
// increment the total request count
s.requests++
// increment the error count
if err != nil {
s.errors++
}
return nil
}
// NewStats returns a new in memory stats buffer
// TODO add options
func NewStats() stats.Stats {
return &memoryStats{
started: time.Now().Unix(),
buffer: ring.New(1),
}
}

View File

@@ -1,32 +0,0 @@
// Package stats provides runtime stats
package stats
// Stats provides stats interface
type Stats interface {
// Read stat snapshot
Read() ([]*Stat, error)
// Write a stat snapshot
Write(*Stat) error
// Record a request
Record(error) error
}
// A runtime stat
type Stat struct {
// Timestamp of recording
Timestamp int64
// Start time as unix timestamp
Started int64
// Uptime in seconds
Uptime int64
// Memory usage in bytes
Memory uint64
// Threads aka go routines
Threads uint64
// Garbage collection in nanoseconds
GC uint64
// Total requests
Requests uint64
// Total errors
Errors uint64
}

View File

@@ -9,33 +9,33 @@ import (
)
var (
// ErrBadRequest
// ErrBadRequest returns then requests contains invalid data
ErrBadRequest = &Error{Code: 400}
// ErrUnauthorized
// ErrUnauthorized returns then user have unauthorized call
ErrUnauthorized = &Error{Code: 401}
// ErrForbidden
// ErrForbidden returns then user have not access the resource
ErrForbidden = &Error{Code: 403}
// ErrNotFound
// ErrNotFound returns then user specify invalid endpoint
ErrNotFound = &Error{Code: 404}
// ErrMethodNotAllowed
// ErrMethodNotAllowed returns then user try to get invalid method
ErrMethodNotAllowed = &Error{Code: 405}
// ErrTimeout
// ErrTimeout returns then timeout exceeded
ErrTimeout = &Error{Code: 408}
// ErrConflict
// ErrConflict returns then request create duplicate resource
ErrConflict = &Error{Code: 409}
// ErrInternalServerError
// ErrInternalServerError returns then server cant process request because of internal error
ErrInternalServerError = &Error{Code: 500}
// ErNotImplemented
// ErNotImplemented returns then server does not have desired endpoint method
ErNotImplemented = &Error{Code: 501}
// ErrBadGateway
// ErrBadGateway returns then server cant process request
ErrBadGateway = &Error{Code: 502}
// ErrServiceUnavailable
// ErrServiceUnavailable returns then service unavailable
ErrServiceUnavailable = &Error{Code: 503}
// ErrGatewayTimeout
// ErrGatewayTimeout returns then server have long time to process request
ErrGatewayTimeout = &Error{Code: 504}
)
// Error tpye
// Error type
type Error struct {
Id string
Code int32
@@ -43,6 +43,7 @@ type Error struct {
Status string
}
// Error satisfies error interface
func (e *Error) Error() string {
b, _ := json.Marshal(e)
return string(b)

21
flow/dag.go Normal file
View File

@@ -0,0 +1,21 @@
package flow
type node struct {
name string
}
func (n *node) ID() string {
return n.name
}
func (n *node) Name() string {
return n.name
}
func (n *node) String() string {
return n.name
}
func (n *node) Hashcode() interface{} {
return n.name
}

68
flow/dag_test.go Normal file
View File

@@ -0,0 +1,68 @@
package flow
import (
"fmt"
"testing"
"github.com/silas/dag"
)
func checkErr(t *testing.T, err error) {
if err != nil {
t.Fatal(err)
}
}
func TestDag(t *testing.T) {
d1 := &dag.AcyclicGraph{}
d2 := &dag.AcyclicGraph{}
d2v1 := d2.Add(&node{"Substep.Create"})
v1 := d1.Add(&node{"AccountService.Create"})
v2 := d1.Add(&node{"AuthzService.Create"})
v3 := d1.Add(&node{"AuthnService.Create"})
v4 := d1.Add(&node{"ProjectService.Create"})
v5 := d1.Add(&node{"ContactService.Create"})
v6 := d1.Add(&node{"NetworkService.Create"})
v7 := d1.Add(&node{"MailerService.Create"})
v8 := d1.Add(&node{"NestedService.Create"})
v9 := d1.Add(d2v1)
d1.Connect(dag.BasicEdge(v1, v2))
d1.Connect(dag.BasicEdge(v1, v3))
d1.Connect(dag.BasicEdge(v1, v4))
d1.Connect(dag.BasicEdge(v1, v5))
d1.Connect(dag.BasicEdge(v1, v6))
d1.Connect(dag.BasicEdge(v1, v7))
d1.Connect(dag.BasicEdge(v7, v8))
d1.Connect(dag.BasicEdge(v8, v9))
if err := d1.Validate(); err != nil {
t.Fatal(err)
}
d1.TransitiveReduction()
var steps [][]string
fn := func(n dag.Vertex, idx int) error {
if idx == 0 {
steps = make([][]string, 1, 1)
steps[0] = make([]string, 0, 1)
} else if idx >= len(steps) {
tsteps := make([][]string, idx+1, idx+1)
copy(tsteps, steps)
steps = tsteps
steps[idx] = make([]string, 0, 1)
}
steps[idx] = append(steps[idx], fmt.Sprintf("%s", n))
return nil
}
start := &node{"AccountService.Create"}
err := d1.SortedDepthFirstWalk([]dag.Vertex{start}, fn)
checkErr(t, err)
if len(steps) != 4 {
t.Fatalf("invalid steps: %#+v", steps)
}
if steps[3][0] != "Substep.Create" {
t.Fatalf("invalid last step: %#+v", steps)
}
}

7
flow/flow.go Normal file
View File

@@ -0,0 +1,7 @@
// Package flow is an interface used for saga pattern messaging
package flow
type Step interface {
// Endpoint returns service_name.service_method
Endpoint() string
}

View File

@@ -64,7 +64,7 @@ func newFunction(opts ...Option) Function {
// make context the last thing
fopts = append(fopts, Context(ctx))
service := &service{opts: NewOptions(opts...)}
service := &service{opts: NewOptions(fopts...)}
fn := &function{
cancel: cancel,

View File

@@ -2,25 +2,26 @@
package micro
/*
import (
"context"
"sync"
"testing"
rmemory "github.com/unistack-org/micro-register-memory"
"github.com/unistack-org/micro/v3/register"
)
func TestFunction(t *testing.T) {
var wg sync.WaitGroup
wg.Add(1)
r := rmemory.NewRegister()
r := register.NewRegister()
ctx := context.TODO()
// create service
fn := NewFunction(
Register(r),
Name("test.function"),
AfterStart(func() error {
AfterStart(func(ctx context.Context) error {
wg.Done()
return nil
}),
@@ -61,3 +62,5 @@ func TestFunction(t *testing.T) {
t.Fatal(err)
}
}
*/

16
go.mod
View File

@@ -1,23 +1,13 @@
module github.com/unistack-org/micro/v3
go 1.14
go 1.16
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/ef-ds/deque v1.0.4
github.com/golang/protobuf v1.4.3
github.com/google/uuid v1.2.0
github.com/imdario/mergo v0.3.11
github.com/kr/text v0.2.0 // indirect
github.com/miekg/dns v1.1.38
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/stretchr/testify v1.7.0
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad // indirect
golang.org/x/net v0.0.0-20210119194325-5f4716e94777
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect
google.golang.org/protobuf v1.25.0
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
github.com/silas/dag v0.0.0-20210121180416-41cf55125c34
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110
)

115
go.sum
View File

@@ -1,128 +1,21 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/ef-ds/deque v1.0.4 h1:iFAZNmveMT9WERAkqLJ+oaABF9AcVQ5AjXem/hroniI=
github.com/ef-ds/deque v1.0.4/go.mod h1:gXDnTC3yqvBcHbq2lcExjtAcVrOnJCbMcZXmuj8Z4tg=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA=
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/miekg/dns v1.1.38 h1:MtIY+fmHUVVgv1AXzmKMWcwdCYxTRPG1EDjpqF4RCEw=
github.com/miekg/dns v1.1.38/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw=
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
github.com/silas/dag v0.0.0-20210121180416-41cf55125c34 h1:vBfVmA5mZhsQa2jr1FOL9nfA37N/jnbBmi5XUfviVTI=
github.com/silas/dag v0.0.0-20210121180416-41cf55125c34/go.mod h1:7RTUFBdIRC9nZ7/3RyRNH1bdqIShrDejd1YbLwgPS+I=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
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.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
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=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@@ -6,7 +6,7 @@ import "context"
var (
// DefaultLogger variable
DefaultLogger Logger = NewLogger()
// DefaultLogger level
// DefaultLevel used by logger
DefaultLevel Level = InfoLevel
)

View File

@@ -16,7 +16,7 @@ func FromIncomingContext(ctx context.Context) (Metadata, bool) {
return nil, false
}
md, ok := ctx.Value(mdIncomingKey{}).(*rawMetadata)
if !ok {
if !ok || md.md == nil {
return nil, false
}
return md.md, ok
@@ -29,7 +29,7 @@ func FromOutgoingContext(ctx context.Context) (Metadata, bool) {
return nil, false
}
md, ok := ctx.Value(mdOutgoingKey{}).(*rawMetadata)
if !ok {
if !ok || md.md == nil {
return nil, false
}
return md.md, ok
@@ -44,7 +44,7 @@ func FromContext(ctx context.Context) (Metadata, bool) {
return nil, false
}
md, ok := ctx.Value(mdKey{}).(*rawMetadata)
if !ok {
if !ok || md.md == nil {
return nil, false
}
return md.md, ok
@@ -93,7 +93,9 @@ func NewIncomingContext(ctx context.Context, md Metadata) context.Context {
ctx = context.Background()
}
ctx = context.WithValue(ctx, mdIncomingKey{}, &rawMetadata{md})
ctx = context.WithValue(ctx, mdOutgoingKey{}, &rawMetadata{})
if v, ok := ctx.Value(mdOutgoingKey{}).(*rawMetadata); !ok || v == nil {
ctx = context.WithValue(ctx, mdOutgoingKey{}, &rawMetadata{})
}
return ctx
}
@@ -103,6 +105,40 @@ func NewOutgoingContext(ctx context.Context, md Metadata) context.Context {
ctx = context.Background()
}
ctx = context.WithValue(ctx, mdOutgoingKey{}, &rawMetadata{md})
ctx = context.WithValue(ctx, mdIncomingKey{}, &rawMetadata{})
if v, ok := ctx.Value(mdIncomingKey{}).(*rawMetadata); !ok || v == nil {
ctx = context.WithValue(ctx, mdIncomingKey{}, &rawMetadata{})
}
return ctx
}
// AppendOutgoingContext apends new md to context
func AppendOutgoingContext(ctx context.Context, kv ...string) context.Context {
md, ok := Pairs(kv...)
if !ok {
return ctx
}
omd, ok := FromOutgoingContext(ctx)
if !ok {
return NewOutgoingContext(ctx, md)
}
for k, v := range md {
omd.Set(k, v)
}
return NewOutgoingContext(ctx, omd)
}
// AppendIncomingContext apends new md to context
func AppendIncomingContext(ctx context.Context, kv ...string) context.Context {
md, ok := Pairs(kv...)
if !ok {
return ctx
}
omd, ok := FromIncomingContext(ctx)
if !ok {
return NewIncomingContext(ctx, md)
}
for k, v := range md {
omd.Set(k, v)
}
return NewIncomingContext(ctx, omd)
}

View File

@@ -25,6 +25,7 @@ var (
defaultMetadataSize = 2
)
// Iterator used to iterate over metadata with order
type Iterator struct {
cur int
cnt int
@@ -32,6 +33,7 @@ type Iterator struct {
md Metadata
}
// Next advance iterator to next element
func (iter *Iterator) Next(k, v *string) bool {
if iter.cur+1 > iter.cnt {
return false
@@ -43,7 +45,7 @@ func (iter *Iterator) Next(k, v *string) bool {
return true
}
// Iterate returns run user func with map key, val sorted by key
// Iterator returns the itarator for metadata in sorted order
func (md Metadata) Iterator() *Iterator {
iter := &Iterator{md: md, cnt: len(md)}
iter.keys = make([]string, 0, iter.cnt)
@@ -109,3 +111,19 @@ func Merge(omd Metadata, mmd Metadata, overwrite bool) Metadata {
}
return nmd
}
func Pairs(kv ...string) (Metadata, bool) {
if len(kv)%2 == 1 {
return nil, false
}
md := New(len(kv) / 2)
var k string
for i, v := range kv {
if i%2 == 0 {
k = v
continue
}
md.Set(k, v)
}
return md, true
}

View File

@@ -2,10 +2,31 @@ package metadata
import (
"context"
"fmt"
"testing"
)
func TestAppend(t *testing.T) {
ctx := context.Background()
ctx = AppendIncomingContext(ctx, "key1", "val1", "key2", "val2")
md, ok := FromIncomingContext(ctx)
if !ok {
t.Fatal("metadata empty")
}
if _, ok := md.Get("key1"); !ok {
t.Fatal("key1 not found")
}
}
func TestPairs(t *testing.T) {
md, ok := Pairs("key1", "val1", "key2", "val2")
if !ok {
t.Fatal("odd number of kv")
}
if _, ok = md.Get("key1"); !ok {
t.Fatal("key1 not found")
}
}
func testCtx(ctx context.Context) {
md := New(2)
md.Set("Key1", "Val1_new")
@@ -25,7 +46,9 @@ func TestPassing(t *testing.T) {
if !ok {
t.Fatalf("missing metadata from outgoing context")
}
fmt.Printf("%#+v\n", md)
if v, ok := md.Get("Key1"); !ok || v != "Val1_new" {
t.Fatalf("invalid metadata value %#+v", md)
}
}
func TestMerge(t *testing.T) {

View File

@@ -14,26 +14,28 @@ var (
DefaultAddress = ":9090"
// DefaultPath the meter endpoint where the Meter data will be made available
DefaultPath = "/metrics"
// timingObjectives is the default spread of stats we maintain for timings / histograms:
//defaultTimingObjectives = map[float64]float64{0.0: 0, 0.5: 0.05, 0.75: 0.04, 0.90: 0.03, 0.95: 0.02, 0.98: 0.001, 1: 0}
// default metric prefix
// DefaultMetricPrefix holds the string that prepends to all metrics
DefaultMetricPrefix = "micro_"
// default label prefix
// DefaultLabelPrefix holds the string that prepends to all labels
DefaultLabelPrefix = "micro_"
// DefaultSummaryQuantiles is the default spread of stats for summary
DefaultSummaryQuantiles = []float64{0.5, 0.9, 0.97, 0.99, 1}
// DefaultSummaryWindow is the default window for summary
DefaultSummaryWindow = 5 * time.Minute
)
// Meter is an interface for collecting and instrumenting metrics
type Meter interface {
Name() string
Init(...Option) error
Counter(string, ...Option) Counter
FloatCounter(string, ...Option) FloatCounter
Gauge(string, func() float64, ...Option) Gauge
Set(...Option) Meter
Histogram(string, ...Option) Histogram
Summary(string, ...Option) Summary
SummaryExt(string, time.Duration, []float64, ...Option) Summary
Write(io.Writer, bool) error
Init(opts ...Option) error
Counter(name string, opts ...Option) Counter
FloatCounter(name string, opts ...Option) FloatCounter
Gauge(name string, fn func() float64, opts ...Option) Gauge
Set(opts ...Option) Meter
Histogram(name string, opts ...Option) Histogram
Summary(name string, opts ...Option) Summary
SummaryExt(name string, window time.Duration, quantiles []float64, opts ...Option) Summary
Write(w io.Writer, opts ...Option) error
Options() Options
String() string
}
@@ -74,28 +76,13 @@ type Summary interface {
UpdateDuration(time.Time)
}
// Labels holds the metrics labels with k, v
type Labels struct {
keys []string
vals []string
}
func (ls Labels) Len() int {
return len(ls.keys)
}
func (ls Labels) Swap(i, j int) {
ls.keys[i], ls.keys[j] = ls.keys[j], ls.keys[i]
ls.vals[i], ls.vals[j] = ls.vals[j], ls.vals[i]
}
func (ls Labels) Less(i, j int) bool {
return ls.vals[i] < ls.vals[j]
}
func (ls Labels) Sort() {
sort.Sort(ls)
}
// Append adds labels to label set
func (ls Labels) Append(nls Labels) Labels {
for n := range nls.keys {
ls.keys = append(ls.keys, nls.keys[n])
@@ -104,17 +91,44 @@ func (ls Labels) Append(nls Labels) Labels {
return ls
}
// Len returns number of labels
func (ls Labels) Len() int {
return len(ls.keys)
}
type labels Labels
func (ls labels) Len() int {
return len(ls.keys)
}
func (ls labels) Sort() {
sort.Sort(ls)
}
func (ls labels) Swap(i, j int) {
ls.keys[i], ls.keys[j] = ls.keys[j], ls.keys[i]
ls.vals[i], ls.vals[j] = ls.vals[j], ls.vals[i]
}
func (ls labels) Less(i, j int) bool {
return ls.keys[i] < ls.keys[j]
}
// LabelIter holds the
type LabelIter struct {
labels Labels
cnt int
cur int
}
// Iter returns labels iterator
func (ls Labels) Iter() *LabelIter {
ls.Sort()
labels(ls).Sort()
return &LabelIter{labels: ls, cnt: len(ls.keys)}
}
// Next advance itarator to new pos
func (iter *LabelIter) Next(k, v *string) bool {
if iter.cur+1 > iter.cnt {
return false

View File

@@ -2,31 +2,16 @@ package meter
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestNoopMeter(t *testing.T) {
meter := NewMeter(Path("/noop"))
assert.NotNil(t, meter)
assert.Equal(t, "/noop", meter.Options().Path)
assert.Implements(t, new(Meter), meter)
cnt := meter.Counter("counter", Label("server", "noop"))
cnt.Inc()
}
func TestLabels(t *testing.T) {
var ls Labels
ls.keys = []string{"type", "server"}
ls.vals = []string{"noop", "http"}
ls.Sort()
if ls.keys[0] != "server" || ls.vals[0] != "http" {
t.Fatalf("sort error: %v", ls)
m := NewMeter(Path("/noop"))
if "/noop" != m.Options().Path {
t.Fatalf("invalid options parsing: %v", m.Options())
}
cnt := m.Counter("counter", Label("server", "noop"))
cnt.Inc()
}
func TestLabelsAppend(t *testing.T) {
@@ -39,23 +24,30 @@ func TestLabelsAppend(t *testing.T) {
nls.vals = []string{"gossip"}
ls = ls.Append(nls)
ls.Sort()
//ls.Sort()
if ls.keys[0] != "register" || ls.vals[0] != "gossip" {
if ls.keys[0] != "type" || ls.vals[0] != "noop" {
t.Fatalf("append error: %v", ls)
}
}
func TestIterator(t *testing.T) {
var ls Labels
ls.keys = []string{"type", "server", "register"}
ls.vals = []string{"noop", "http", "gossip"}
options := NewOptions(
Label("name", "svc1"),
Label("version", "0.0.1"),
Label("id", "12345"),
Label("type", "noop"),
Label("server", "http"),
Label("register", "gossip"),
Label("aa", "kk"),
Label("zz", "kk"),
)
iter := ls.Iter()
iter := options.Labels.Iter()
var k, v string
cnt := 0
for iter.Next(&k, &v) {
if cnt == 1 && (k != "server" || v != "http") {
if cnt == 4 && (k != "server" || v != "http") {
t.Fatalf("iter error: %s != %s || %s != %s", k, "server", v, "http")
}
cnt++

View File

@@ -7,8 +7,7 @@ import (
// NoopMeter is an noop implementation of Meter
type noopMeter struct {
opts Options
labels Labels
opts Options
}
// NewMeter returns a configured noop reporter:
@@ -39,27 +38,47 @@ func (r *noopMeter) Counter(name string, opts ...Option) Counter {
// FloatCounter implements the Meter interface
func (r *noopMeter) FloatCounter(name string, opts ...Option) FloatCounter {
return &noopFloatCounter{}
options := Options{}
for _, o := range opts {
o(&options)
}
return &noopFloatCounter{labels: options.Labels}
}
// Gauge implements the Meter interface
func (r *noopMeter) Gauge(name string, f func() float64, opts ...Option) Gauge {
return &noopGauge{}
options := Options{}
for _, o := range opts {
o(&options)
}
return &noopGauge{labels: options.Labels}
}
// Summary implements the Meter interface
func (r *noopMeter) Summary(name string, opts ...Option) Summary {
return &noopSummary{}
options := Options{}
for _, o := range opts {
o(&options)
}
return &noopSummary{labels: options.Labels}
}
// SummaryExt implements the Meter interface
func (r *noopMeter) SummaryExt(name string, window time.Duration, quantiles []float64, opts ...Option) Summary {
return &noopSummary{}
options := Options{}
for _, o := range opts {
o(&options)
}
return &noopSummary{labels: options.Labels}
}
// Histogram implements the Meter interface
func (r *noopMeter) Histogram(name string, opts ...Option) Histogram {
return &noopHistogram{}
options := Options{}
for _, o := range opts {
o(&options)
}
return &noopHistogram{labels: options.Labels}
}
// Set implements the Meter interface
@@ -73,7 +92,7 @@ func (r *noopMeter) Set(opts ...Option) Meter {
return m
}
func (r *noopMeter) Write(w io.Writer, withProcessMetrics bool) error {
func (r *noopMeter) Write(w io.Writer, opts ...Option) error {
return nil
}
@@ -111,7 +130,9 @@ func (r *noopCounter) Set(uint64) {
}
type noopFloatCounter struct{}
type noopFloatCounter struct {
labels Labels
}
func (r *noopFloatCounter) Add(float64) {
@@ -129,13 +150,17 @@ func (r *noopFloatCounter) Sub(float64) {
}
type noopGauge struct{}
type noopGauge struct {
labels Labels
}
func (r *noopGauge) Get() float64 {
return 0
}
type noopSummary struct{}
type noopSummary struct {
labels Labels
}
func (r *noopSummary) Update(float64) {
@@ -145,7 +170,9 @@ func (r *noopSummary) UpdateDuration(time.Time) {
}
type noopHistogram struct{}
type noopHistogram struct {
labels Labels
}
func (r *noopHistogram) Reset() {

View File

@@ -16,10 +16,12 @@ type Options struct {
Path string
Labels Labels
//TimingObjectives map[float64]float64
Logger logger.Logger
Context context.Context
MetricPrefix string
LabelPrefix string
Logger logger.Logger
Context context.Context
MetricPrefix string
LabelPrefix string
WriteProcessMetrics bool
WriteFDMetrics bool
}
// NewOptions prepares a set of options:
@@ -61,15 +63,6 @@ func Address(value string) Option {
}
}
/*
// Labels be added to every metric
func Labels(labels []string) Option {
return func(o *Options) {
o.Labels = labels
}
}
*/
/*
// TimingObjectives defines the desired spread of statistics for histogram / timing metrics:
func TimingObjectives(value map[float64]float64) Option {
@@ -100,3 +93,17 @@ func Name(n string) Option {
o.Name = n
}
}
// WriteProcessMetrics enable process metrics output for write
func WriteProcessMetrics(b bool) Option {
return func(o *Options) {
o.WriteProcessMetrics = b
}
}
// WriteFDMetrics enable fd metrics output for write
func WriteFDMetrics(b bool) Option {
return func(o *Options) {
o.WriteFDMetrics = b
}
}

View File

@@ -1,5 +1,3 @@
// +build ignore
package wrapper
import (
@@ -12,30 +10,56 @@ import (
"github.com/unistack-org/micro/v3/server"
)
var (
ClientRequestDurationSeconds = "client_request_duration_seconds"
ClientRequestLatencyMicroseconds = "client_request_latency_microseconds"
ClientRequestTotal = "client_request_total"
ServerRequestDurationSeconds = "server_request_duration_seconds"
ServerRequestLatencyMicroseconds = "server_request_latency_microseconds"
ServerRequestTotal = "server_request_total"
PublishMessageDurationSeconds = "publish_message_duration_seconds"
PublishMessageLatencyMicroseconds = "publish_message_latency_microseconds"
PublishMessageTotal = "publish_message_total"
SubscribeMessageDurationSeconds = "subscribe_message_duration_seconds"
SubscribeMessageLatencyMicroseconds = "subscribe_message_latency_microseconds"
SubscribeMessageTotal = "subscribe_message_total"
labelSuccess = "success"
labelFailure = "failure"
labelStatus = "status"
labelEndpoint = "endpoint"
)
type Options struct {
Meter meter.Meter
Name string
Version string
ID string
Meter meter.Meter
lopts []meter.Option
}
type Option func(*Options)
func NewOptions(opts ...Option) Options {
options := Options{lopts: make([]meter.Option, 0, 5)}
for _, o := range opts {
o(&options)
}
return options
}
func ServiceName(name string) Option {
return func(o *Options) {
o.Name = name
o.lopts = append(o.lopts, meter.Label("name", name))
}
}
func ServiceVersion(version string) Option {
return func(o *Options) {
o.Version = version
o.lopts = append(o.lopts, meter.Label("version", version))
}
}
func ServiceID(id string) Option {
return func(o *Options) {
o.ID = id
o.lopts = append(o.lopts, meter.Label("id", id))
}
}
@@ -46,7 +70,7 @@ func Meter(m meter.Meter) Option {
}
type wrapper struct {
options Options
opts Options
callFunc client.CallFunc
client.Client
}
@@ -54,178 +78,174 @@ type wrapper struct {
func NewClientWrapper(opts ...Option) client.Wrapper {
return func(c client.Client) client.Client {
handler := &wrapper{
labels: labels,
opts: NewOptions(opts...),
Client: c,
}
return handler
}
}
func NewCallWrapper(opts ...Option) client.CallWrapper {
labels := getLabels(opts...)
return func(fn client.CallFunc) client.CallFunc {
handler := &wrapper{
labels: labels,
opts: NewOptions(opts...),
callFunc: fn,
}
return handler.CallFunc
}
}
func (w *wrapper) CallFunc(ctx context.Context, addr string, req client.Request, rsp interface{}, opts client.CallOptions) error {
endpoint := fmt.Sprintf("%s.%s", req.Service(), req.Endpoint())
wlabels := append(w.labels, fmt.Sprintf(`%sendpoint="%s"`, DefaultLabelPrefix, endpoint))
timeCounterSummary := metrics.GetOrCreateSummary(getName("client_request_latency_microseconds", wlabels))
timeCounterHistogram := metrics.GetOrCreateSummary(getName("client_request_duration_seconds", wlabels))
ts := time.Now()
err := w.callFunc(ctx, addr, req, rsp, opts)
te := time.Since(ts)
timeCounterSummary.Update(float64(te.Seconds()))
timeCounterHistogram.Update(te.Seconds())
lopts := w.opts.lopts
lopts = append(lopts, meter.Label(labelEndpoint, endpoint))
w.opts.Meter.Summary(ClientRequestLatencyMicroseconds, lopts...).Update(float64(te.Seconds()))
w.opts.Meter.Histogram(ClientRequestDurationSeconds, lopts...).Update(float64(te.Seconds()))
if err == nil {
metrics.GetOrCreateCounter(getName("client_request_total", append(wlabels, fmt.Sprintf(`%sstatus="success"`, DefaultLabelPrefix)))).Inc()
lopts = append(lopts, meter.Label(labelStatus, labelSuccess))
} else {
metrics.GetOrCreateCounter(getName("client_request_total", append(wlabels, fmt.Sprintf(`%sstatus="failure"`, DefaultLabelPrefix)))).Inc()
lopts = append(lopts, meter.Label(labelStatus, labelFailure))
}
w.opts.Meter.Counter(ClientRequestTotal, lopts...).Inc()
return err
}
func (w *wrapper) Call(ctx context.Context, req client.Request, rsp interface{}, opts ...client.CallOption) error {
endpoint := fmt.Sprintf("%s.%s", req.Service(), req.Endpoint())
wlabels := append(w.labels, fmt.Sprintf(`%sendpoint="%s"`, DefaultLabelPrefix, endpoint))
timeCounterSummary := metrics.GetOrCreateSummary(getName("client_request_latency_microseconds", wlabels))
timeCounterHistogram := metrics.GetOrCreateSummary(getName("client_request_duration_seconds", wlabels))
ts := time.Now()
err := w.Client.Call(ctx, req, rsp, opts...)
te := time.Since(ts)
timeCounterSummary.Update(float64(te.Seconds()))
timeCounterHistogram.Update(te.Seconds())
lopts := w.opts.lopts
lopts = append(lopts, meter.Label(labelEndpoint, endpoint))
w.opts.Meter.Summary(ClientRequestLatencyMicroseconds, lopts...).Update(float64(te.Seconds()))
w.opts.Meter.Histogram(ClientRequestDurationSeconds, lopts...).Update(float64(te.Seconds()))
if err == nil {
metrics.GetOrCreateCounter(getName("client_request_total", append(wlabels, fmt.Sprintf(`%sstatus="success"`, DefaultLabelPrefix)))).Inc()
lopts = append(lopts, meter.Label(labelStatus, labelSuccess))
} else {
metrics.GetOrCreateCounter(getName("client_request_total", append(wlabels, fmt.Sprintf(`%sstatus="failure"`, DefaultLabelPrefix)))).Inc()
lopts = append(lopts, meter.Label(labelStatus, labelFailure))
}
w.opts.Meter.Counter(ClientRequestTotal, lopts...).Inc()
return err
}
func (w *wrapper) Stream(ctx context.Context, req client.Request, opts ...client.CallOption) (client.Stream, error) {
endpoint := fmt.Sprintf("%s.%s", req.Service(), req.Endpoint())
wlabels := append(w.labels, fmt.Sprintf(`%sendpoint="%s"`, DefaultLabelPrefix, endpoint))
timeCounterSummary := metrics.GetOrCreateSummary(getName("client_request_latency_microseconds", wlabels))
timeCounterHistogram := metrics.GetOrCreateSummary(getName("client_request_duration_seconds", wlabels))
ts := time.Now()
stream, err := w.Client.Stream(ctx, req, opts...)
te := time.Since(ts)
timeCounterSummary.Update(float64(te.Seconds()))
timeCounterHistogram.Update(te.Seconds())
lopts := w.opts.lopts
lopts = append(lopts, meter.Label(labelEndpoint, endpoint))
w.opts.Meter.Summary(ClientRequestLatencyMicroseconds, lopts...).Update(float64(te.Seconds()))
w.opts.Meter.Histogram(ClientRequestDurationSeconds, lopts...).Update(float64(te.Seconds()))
if err == nil {
metrics.GetOrCreateCounter(getName("client_request_total", append(wlabels, fmt.Sprintf(`%sstatus="success"`, DefaultLabelPrefix)))).Inc()
lopts = append(lopts, meter.Label(labelStatus, labelSuccess))
} else {
metrics.GetOrCreateCounter(getName("client_request_total", append(wlabels, fmt.Sprintf(`%sstatus="failure"`, DefaultLabelPrefix)))).Inc()
lopts = append(lopts, meter.Label(labelStatus, labelFailure))
}
w.opts.Meter.Counter(ClientRequestTotal, lopts...).Inc()
return stream, err
}
func (w *wrapper) Publish(ctx context.Context, p client.Message, opts ...client.PublishOption) error {
endpoint := p.Topic()
wlabels := append(w.labels, fmt.Sprintf(`%sendpoint="%s"`, DefaultLabelPrefix, endpoint))
timeCounterSummary := metrics.GetOrCreateSummary(getName("publish_message_latency_microseconds", wlabels))
timeCounterHistogram := metrics.GetOrCreateSummary(getName("publish_message_duration_seconds", wlabels))
ts := time.Now()
err := w.Client.Publish(ctx, p, opts...)
te := time.Since(ts)
timeCounterSummary.Update(float64(te.Seconds()))
timeCounterHistogram.Update(te.Seconds())
lopts := w.opts.lopts
lopts = append(lopts, meter.Label(labelEndpoint, endpoint))
w.opts.Meter.Summary(PublishMessageLatencyMicroseconds, lopts...).Update(float64(te.Seconds()))
w.opts.Meter.Histogram(PublishMessageDurationSeconds, lopts...).Update(float64(te.Seconds()))
if err == nil {
metrics.GetOrCreateCounter(getName("publish_message_total", append(wlabels, fmt.Sprintf(`%sstatus="success"`, DefaultLabelPrefix)))).Inc()
lopts = append(lopts, meter.Label(labelStatus, labelSuccess))
} else {
metrics.GetOrCreateCounter(getName("publish_message_total", append(wlabels, fmt.Sprintf(`%sstatus="failure"`, DefaultLabelPrefix)))).Inc()
lopts = append(lopts, meter.Label(labelStatus, labelFailure))
}
w.opts.Meter.Counter(PublishMessageTotal, lopts...).Inc()
return err
}
func NewHandlerWrapper(opts ...Option) server.HandlerWrapper {
labels := getLabels(opts...)
handler := &wrapper{
labels: labels,
opts: NewOptions(opts...),
}
return handler.HandlerFunc
}
func (w *wrapper) HandlerFunc(fn server.HandlerFunc) server.HandlerFunc {
return func(ctx context.Context, req server.Request, rsp interface{}) error {
endpoint := req.Endpoint()
wlabels := append(w.labels, fmt.Sprintf(`%sendpoint="%s"`, DefaultLabelPrefix, endpoint))
timeCounterSummary := metrics.GetOrCreateSummary(getName("server_request_latency_microseconds", wlabels))
timeCounterHistogram := metrics.GetOrCreateSummary(getName("server_request_duration_seconds", wlabels))
ts := time.Now()
err := fn(ctx, req, rsp)
te := time.Since(ts)
timeCounterSummary.Update(float64(te.Seconds()))
timeCounterHistogram.Update(te.Seconds())
lopts := w.opts.lopts
lopts = append(lopts, meter.Label(labelEndpoint, endpoint))
w.opts.Meter.Summary(ServerRequestLatencyMicroseconds, lopts...).Update(float64(te.Seconds()))
w.opts.Meter.Histogram(ServerRequestDurationSeconds, lopts...).Update(float64(te.Seconds()))
if err == nil {
metrics.GetOrCreateCounter(getName("server_request_total", append(wlabels, fmt.Sprintf(`%sstatus="success"`, DefaultLabelPrefix)))).Inc()
lopts = append(lopts, meter.Label(labelStatus, labelSuccess))
} else {
metrics.GetOrCreateCounter(getName("server_request_total", append(wlabels, fmt.Sprintf(`%sstatus="failure"`, DefaultLabelPrefix)))).Inc()
lopts = append(lopts, meter.Label(labelStatus, labelFailure))
}
w.opts.Meter.Counter(ServerRequestTotal, lopts...).Inc()
return err
}
}
func NewSubscriberWrapper(opts ...Option) server.SubscriberWrapper {
labels := getLabels(opts...)
handler := &wrapper{
labels: labels,
opts: NewOptions(opts...),
}
return handler.SubscriberFunc
}
func (w *wrapper) SubscriberFunc(fn server.SubscriberFunc) server.SubscriberFunc {
return func(ctx context.Context, msg server.Message) error {
endpoint := msg.Topic()
wlabels := append(w.labels, fmt.Sprintf(`%sendpoint="%s"`, DefaultLabelPrefix, endpoint))
timeCounterSummary := metrics.GetOrCreateSummary(getName("subscribe_message_latency_microseconds", wlabels))
timeCounterHistogram := metrics.GetOrCreateSummary(getName("subscribe_message_duration_seconds", wlabels))
ts := time.Now()
err := fn(ctx, msg)
te := time.Since(ts)
timeCounterSummary.Update(float64(te.Seconds()))
timeCounterHistogram.Update(te.Seconds())
lopts := w.opts.lopts
lopts = append(lopts, meter.Label(labelEndpoint, endpoint))
w.opts.Meter.Summary(SubscribeMessageLatencyMicroseconds, lopts...).Update(float64(te.Seconds()))
w.opts.Meter.Histogram(SubscribeMessageDurationSeconds, lopts...).Update(float64(te.Seconds()))
if err == nil {
metrics.GetOrCreateCounter(getName("subscribe_message_total", append(wlabels, fmt.Sprintf(`%sstatus="success"`, DefaultLabelPrefix)))).Inc()
lopts = append(lopts, meter.Label(labelStatus, labelSuccess))
} else {
metrics.GetOrCreateCounter(getName("subscribe_message_total", append(wlabels, fmt.Sprintf(`%sstatus="failure"`, DefaultLabelPrefix)))).Inc()
lopts = append(lopts, meter.Label(labelStatus, labelFailure))
}
w.opts.Meter.Counter(SubscribeMessageTotal, lopts...).Inc()
return err
}

View File

@@ -1,3 +1,5 @@
// +build ignore
// Package model is an interface for data modelling
package model

View File

@@ -1,3 +1,5 @@
// +build ignore
// Package model is an interface for data modelling
package model

View File

@@ -4,13 +4,13 @@ import (
"context"
"errors"
"fmt"
"math/rand"
"net"
"sync"
"time"
maddr "github.com/unistack-org/micro/v3/util/addr"
mnet "github.com/unistack-org/micro/v3/util/net"
"github.com/unistack-org/micro/v3/util/rand"
)
type memorySocket struct {
@@ -207,7 +207,8 @@ func (m *memoryTransport) Listen(ctx context.Context, addr string, opts ...Liste
// if zero port then randomly assign one
if len(port) > 0 && port == "0" {
i := rand.Intn(20000)
var rng rand.Rand
i := rng.Intn(20000)
port = fmt.Sprintf("%d", 10000+i)
}
@@ -251,11 +252,10 @@ func (m *memoryTransport) Name() string {
return m.opts.Name
}
// NewTransport returns new memory transport with options
func NewTransport(opts ...Option) Transport {
options := NewOptions(opts...)
rand.Seed(time.Now().UnixNano())
return &memoryTransport{
opts: options,
listeners: make(map[string]*memoryListener),

View File

@@ -11,6 +11,7 @@ import (
"github.com/unistack-org/micro/v3/tracer"
)
// Options struct holds the transport options
type Options struct {
Name string
// Addrs is the list of intermediary addresses to connect to
@@ -18,10 +19,6 @@ type Options struct {
// Codec is the codec interface to use where headers are not supported
// by the transport and the entire payload must be encoded
Codec codec.Codec
// Secure tells the transport to secure the connection.
// In the case TLSConfig is not specified best effort self-signed
// certs should be used
Secure bool
// TLSConfig to secure the connection. The assumption is that this
// is mTLS keypair
TLSConfig *tls.Config
@@ -31,7 +28,7 @@ type Options struct {
Logger logger.Logger
// Meter sets the meter
Meter meter.Meter
// Tracer
// Tracer sets the tracer
Tracer tracer.Tracer
// Other options for implementations of the interface
// can be stored in a context

View File

@@ -11,7 +11,7 @@ import (
var (
// DefaultTransport is the global default transport
DefaultTransport Transport = NewTransport()
// Default dial timeout
// DefaultDialTimeout the default dial timeout
DefaultDialTimeout = time.Second * 5
)

View File

@@ -175,6 +175,7 @@ func (t *tunEvent) Error() error {
return nil
}
// NewBroker returns new tunnel broker
func NewBroker(opts ...broker.Option) (broker.Broker, error) {
options := broker.NewOptions(opts...)

View File

@@ -13,11 +13,11 @@ import (
var (
// DefaultAddress is default tunnel bind address
DefaultAddress = ":0"
// The shared default token
// DefaultToken the shared default token
DefaultToken = "go.micro.tunnel"
)
// Option func
// Option func signature
type Option func(*Options)
// Options provides network configuration options
@@ -160,7 +160,7 @@ func DialWait(b bool) DialOption {
}
}
// DefaultOptions returns router default options
// NewOptions returns router default options with filled values
func NewOptions(opts ...Option) Options {
options := Options{
Id: uuid.New().String(),

View File

@@ -88,7 +88,7 @@ func NewTransport(opts ...transport.Option) transport.Transport {
}
// initialise
t.Init(opts...)
//t.Init(opts...)
return t
}

View File

@@ -10,15 +10,16 @@ import (
)
var (
// DefaultTunnel contains default tunnel implementation
DefaultTunnel Tunnel
)
const (
// send over one link
// Unicast send over one link
Unicast Mode = iota
// send to all channel listeners
// Multicast send to all channel listeners
Multicast
// send to all links
// Broadcast send to all links
Broadcast
)
@@ -33,7 +34,7 @@ var (
ErrLinkNotFound = errors.New("link not found")
// ErrLinkDisconnected is returned when a link we attempt to send to is disconnected
ErrLinkDisconnected = errors.New("link not connected")
// ErrLinkLoppback is returned when attempting to send an outbound message over loopback link
// ErrLinkLoopback is returned when attempting to send an outbound message over loopback link
ErrLinkLoopback = errors.New("link is loopback")
// ErrLinkRemote is returned when attempting to send a loopback message over remote link
ErrLinkRemote = errors.New("link is remote")
@@ -87,7 +88,7 @@ type Link interface {
transport.Socket
}
// The listener provides similar constructs to the transport.Listener
// Listener provides similar constructs to the transport.Listener
type Listener interface {
Accept() (Session, error)
Channel() string

View File

@@ -17,7 +17,7 @@ import (
"github.com/unistack-org/micro/v3/server"
"github.com/unistack-org/micro/v3/store"
"github.com/unistack-org/micro/v3/tracer"
// "github.com/unistack-org/micro/v3/debug/profile"
// "github.com/unistack-org/micro/v3/profiler"
// "github.com/unistack-org/micro/v3/runtime"
)
@@ -72,6 +72,7 @@ func NewOptions(opts ...Option) Options {
}
for _, o := range opts {
//nolint:errcheck
o(&options)
}
@@ -120,14 +121,17 @@ type brokerOptions struct {
clients []string
}
// BrokerOption func signature
type BrokerOption func(*brokerOptions)
// BrokerClient specifies clients for broker
func BrokerClient(n string) BrokerOption {
return func(o *brokerOptions) {
o.clients = append(o.clients, n)
}
}
// BrokerServer specifies servers for broker
func BrokerServer(n string) BrokerOption {
return func(o *brokerOptions) {
o.servers = append(o.servers, n)
@@ -202,6 +206,7 @@ func Stores(s ...store.Store) Option {
}
// Logger set the logger to use
//nolint:gocyclo
func Logger(l logger.Logger, opts ...LoggerOption) Option {
return func(o *Options) error {
var err error
@@ -281,8 +286,10 @@ func Logger(l logger.Logger, opts ...LoggerOption) Option {
}
}
// LoggerOption func signature
type LoggerOption func(*loggerOptions)
// loggerOptions
type loggerOptions struct {
servers []string
clients []string
@@ -317,6 +324,7 @@ func Meters(m ...meter.Meter) Option {
// Register sets the register for the service
// and the underlying components
//nolint:gocyclo
func Register(r register.Register, opts ...RegisterOption) Option {
return func(o *Options) error {
var err error
@@ -365,26 +373,32 @@ type registerOptions struct {
brokers []string
}
// RegisterOption func signature
type RegisterOption func(*registerOptions)
// RegisterRouter speciefies routers for register
func RegisterRouter(n string) RegisterOption {
return func(o *registerOptions) {
o.routers = append(o.routers, n)
}
}
// RegisterServer specifies servers for register
func RegisterServer(n string) RegisterOption {
return func(o *registerOptions) {
o.servers = append(o.servers, n)
}
}
// RegisterBroker specifies broker for register
func RegisterBroker(n string) RegisterOption {
return func(o *registerOptions) {
o.brokers = append(o.brokers, n)
}
}
// Tracer sets the tracer
//nolint:gocyclo
func Tracer(t tracer.Tracer, opts ...TracerOption) Option {
return func(o *Options) error {
var err error
@@ -443,26 +457,31 @@ type tracerOptions struct {
stores []string
}
// TracerOption func signature
type TracerOption func(*tracerOptions)
// TracerClient sets the clients for tracer
func TracerClient(n string) TracerOption {
return func(o *tracerOptions) {
o.clients = append(o.clients, n)
}
}
// TracerServer sets the servers for tracer
func TracerServer(n string) TracerOption {
return func(o *tracerOptions) {
o.servers = append(o.servers, n)
}
}
// TracerBroker sets the broker for tracer
func TracerBroker(n string) TracerOption {
return func(o *tracerOptions) {
o.brokers = append(o.brokers, n)
}
}
// TracerStore sets the store for tracer
func TracerStore(n string) TracerOption {
return func(o *tracerOptions) {
o.stores = append(o.stores, n)
@@ -547,8 +566,10 @@ type routerOptions struct {
clients []string
}
// RouterOption func signature
type RouterOption func(*routerOptions)
// RouterClient sets the clients for router
func RouterClient(n string) RouterOption {
return func(o *routerOptions) {
o.clients = append(o.clients, n)

View File

@@ -7,7 +7,7 @@ import (
"net/http/pprof"
"sync"
"github.com/unistack-org/micro/v3/debug/profile"
profile "github.com/unistack-org/micro/v3/profiler"
)
type httpProfile struct {
@@ -17,6 +17,7 @@ type httpProfile struct {
}
var (
// DefaultAddress for http profiler
DefaultAddress = ":6060"
)
@@ -60,7 +61,8 @@ func (h *httpProfile) String() string {
return "http"
}
func NewProfile(opts ...profile.Option) profile.Profile {
// NewProfile returns new http profiler
func NewProfile(opts ...profile.Option) profile.Profiler {
mux := http.NewServeMux()
mux.HandleFunc("/debug/pprof/", pprof.Index)

20
profiler/noop.go Normal file
View File

@@ -0,0 +1,20 @@
package profiler
type noopProfiler struct{}
func (p *noopProfiler) Start() error {
return nil
}
func (p *noopProfiler) Stop() error {
return nil
}
func (p *noopProfiler) String() string {
return "noop"
}
// NewProfiler returns new noop profiler
func NewProfiler(opts ...Option) Profiler {
return &noopProfiler{}
}

View File

@@ -9,7 +9,7 @@ import (
"sync"
"time"
"github.com/unistack-org/micro/v3/debug/profile"
profile "github.com/unistack-org/micro/v3/profiler"
)
type profiler struct {
@@ -111,12 +111,11 @@ func (p *profiler) String() string {
return "pprof"
}
func NewProfile(opts ...profile.Option) profile.Profile {
var options profile.Options
// NewProfile create new profiler
func NewProfile(opts ...profile.Option) profile.Profiler {
options := profile.Options{}
for _, o := range opts {
o(&options)
}
p := new(profiler)
p.opts = options
return p
return &profiler{opts: options}
}

View File

@@ -1,7 +1,8 @@
// Package profile is for profilers
package profile
// Package profiler is for profilers
package profiler
type Profile interface {
// Profiler interface
type Profiler interface {
// Start the profiler
Start() error
// Stop the profiler
@@ -11,28 +12,17 @@ type Profile interface {
}
var (
DefaultProfile Profile = &NoopProfile{}
// DefaultProfiler holds the default profiler
DefaultProfiler Profiler = NewProfiler()
)
type NoopProfile struct{}
func (p *NoopProfile) Start() error {
return nil
}
func (p *NoopProfile) Stop() error {
return nil
}
func (p *NoopProfile) String() string {
return "noop"
}
// Options holds the options for profiler
type Options struct {
// Name to use for the profile
Name string
}
// Option func signature
type Option func(o *Options)
// Name of the profile

View File

@@ -30,6 +30,7 @@ type Options struct {
// Option func signature
type Option func(o *Options)
// NewOptions returns new options struct that filled by opts
func NewOptions(opts ...Option) Options {
options := Options{
Logger: logger.DefaultLogger,

View File

@@ -7,6 +7,11 @@ import (
"github.com/unistack-org/micro/v3/server"
)
var (
// DefaultEndpoint holds default proxy address
DefaultEndpoint = "localhost:9090"
)
// Proxy can be used as a proxy server for micro services
type Proxy interface {
// ProcessMessage handles inbound messages
@@ -16,7 +21,3 @@ type Proxy interface {
// Name of the proxy protocol
String() string
}
var (
DefaultEndpoint = "localhost:9090"
)

View File

@@ -10,7 +10,7 @@ import (
"github.com/unistack-org/micro/v3/metadata"
)
// Extract *Value from reflect.Type
// ExtractValue from reflect.Type from specified depth
func ExtractValue(v reflect.Type, d int) *Value {
if d == 3 {
return nil

View File

@@ -57,26 +57,23 @@ func (m *memory) ttlPrune() {
prune := time.NewTicker(ttlPruneTime)
defer prune.Stop()
for {
select {
case <-prune.C:
m.Lock()
for domain, services := range m.records {
for service, versions := range services {
for version, record := range versions {
for id, n := range record.Nodes {
if n.TTL != 0 && time.Since(n.LastSeen) > n.TTL {
if m.opts.Logger.V(logger.DebugLevel) {
m.opts.Logger.Debugf(m.opts.Context, "Register TTL expired for node %s of service %s", n.Id, service)
}
delete(m.records[domain][service][version].Nodes, id)
for range prune.C {
m.Lock()
for domain, services := range m.records {
for service, versions := range services {
for version, record := range versions {
for id, n := range record.Nodes {
if n.TTL != 0 && time.Since(n.LastSeen) > n.TTL {
if m.opts.Logger.V(logger.DebugLevel) {
m.opts.Logger.Debugf(m.opts.Context, "Register TTL expired for node %s of service %s", n.Id, service)
}
delete(m.records[domain][service][version].Nodes, id)
}
}
}
}
m.Unlock()
}
m.Unlock()
}
}

View File

@@ -75,6 +75,7 @@ var (
}
)
//nolint:gocyclo
func TestMemoryRegistry(t *testing.T) {
ctx := context.TODO()
m := NewRegister()

View File

@@ -10,6 +10,7 @@ import (
"github.com/unistack-org/micro/v3/tracer"
)
// Options holds options for register
type Options struct {
Name string
Addrs []string
@@ -27,6 +28,7 @@ type Options struct {
Context context.Context
}
// NewOptions returns options that filled by opts
func NewOptions(opts ...Option) Options {
options := Options{
Logger: logger.DefaultLogger,
@@ -40,6 +42,7 @@ func NewOptions(opts ...Option) Options {
return options
}
// RegisterOptions holds options for register method
type RegisterOptions struct {
TTL time.Duration
// Other options for implementations of the interface
@@ -51,6 +54,7 @@ type RegisterOptions struct {
Attempts int
}
// NewRegisterOptions returns register options struct filled by opts
func NewRegisterOptions(opts ...RegisterOption) RegisterOptions {
options := RegisterOptions{
Domain: DefaultDomain,
@@ -62,6 +66,7 @@ func NewRegisterOptions(opts ...RegisterOption) RegisterOptions {
return options
}
// WatchOptions holds watch options
type WatchOptions struct {
// Specify a service to watch
// If blank, the watch is for all services
@@ -73,6 +78,7 @@ type WatchOptions struct {
Domain string
}
// NewWatchOptions returns watch options filled by opts
func NewWatchOptions(opts ...WatchOption) WatchOptions {
options := WatchOptions{
Domain: DefaultDomain,
@@ -84,6 +90,7 @@ func NewWatchOptions(opts ...WatchOption) WatchOptions {
return options
}
// DeregisterOptions holds options for deregister method
type DeregisterOptions struct {
Context context.Context
// Domain the service was registered in
@@ -92,6 +99,7 @@ type DeregisterOptions struct {
Attempts int
}
// NewDeregisterOptions returns options for deregister filled by opts
func NewDeregisterOptions(opts ...DeregisterOption) DeregisterOptions {
options := DeregisterOptions{
Domain: DefaultDomain,
@@ -103,12 +111,14 @@ func NewDeregisterOptions(opts ...DeregisterOption) DeregisterOptions {
return options
}
// LookupOptions holds lookup options
type LookupOptions struct {
Context context.Context
// Domain to scope the request to
Domain string
}
// NewLookupOptions returns lookup options filled by opts
func NewLookupOptions(opts ...LookupOption) LookupOptions {
options := LookupOptions{
Domain: DefaultDomain,
@@ -120,12 +130,14 @@ func NewLookupOptions(opts ...LookupOption) LookupOptions {
return options
}
// ListOptions holds the list options for list method
type ListOptions struct {
Context context.Context
// Domain to scope the request to
Domain string
}
// NewListOptions returns list options filled by opts
func NewListOptions(opts ...ListOption) ListOptions {
options := ListOptions{
Domain: DefaultDomain,
@@ -144,6 +156,7 @@ func Addrs(addrs ...string) Option {
}
}
// Timeout sets the timeout
func Timeout(t time.Duration) Option {
return func(o *Options) {
o.Timeout = t
@@ -178,92 +191,105 @@ func Context(ctx context.Context) Option {
}
}
// Specify TLS Config
// TLSConfig Specify TLS Config
func TLSConfig(t *tls.Config) Option {
return func(o *Options) {
o.TLSConfig = t
}
}
// RegisterAttempts specifies register atempts count
func RegisterAttempts(t int) RegisterOption {
return func(o *RegisterOptions) {
o.Attempts = t
}
}
// RegisterTTL specifies register ttl
func RegisterTTL(t time.Duration) RegisterOption {
return func(o *RegisterOptions) {
o.TTL = t
}
}
// RegisterContext sets the register context
func RegisterContext(ctx context.Context) RegisterOption {
return func(o *RegisterOptions) {
o.Context = ctx
}
}
// RegisterDomain secifies register domain
func RegisterDomain(d string) RegisterOption {
return func(o *RegisterOptions) {
o.Domain = d
}
}
// Watch a service
// WatchService name
func WatchService(name string) WatchOption {
return func(o *WatchOptions) {
o.Service = name
}
}
// WatchContext sets the context for watch method
func WatchContext(ctx context.Context) WatchOption {
return func(o *WatchOptions) {
o.Context = ctx
}
}
// WatchDomain sets the domain for watch
func WatchDomain(d string) WatchOption {
return func(o *WatchOptions) {
o.Domain = d
}
}
func DeregisterTimeout(t int) DeregisterOption {
// DeregisterAttempts specifies deregister atempts count
func DeregisterAttempts(t int) DeregisterOption {
return func(o *DeregisterOptions) {
o.Attempts = t
}
}
// DeregisterContext sets the context for deregister method
func DeregisterContext(ctx context.Context) DeregisterOption {
return func(o *DeregisterOptions) {
o.Context = ctx
}
}
// DeregisterDomain specifies deregister domain
func DeregisterDomain(d string) DeregisterOption {
return func(o *DeregisterOptions) {
o.Domain = d
}
}
// LookupContext sets the context for lookup method
func LookupContext(ctx context.Context) LookupOption {
return func(o *LookupOptions) {
o.Context = ctx
}
}
// LookupDomain sets the domain for lookup
func LookupDomain(d string) LookupOption {
return func(o *LookupOptions) {
o.Domain = d
}
}
// ListContext specifies context for list method
func ListContext(ctx context.Context) ListOption {
return func(o *ListOptions) {
o.Context = ctx
}
}
// ListDomain sets the domain for list method
func ListDomain(d string) ListOption {
return func(o *ListOptions) {
o.Domain = d

View File

@@ -4,18 +4,21 @@ package dns
import (
"context"
"net"
"sync"
"time"
"github.com/miekg/dns"
"github.com/unistack-org/micro/v3/resolver"
)
// Resolver is a DNS network resolve
type Resolver struct {
// The resolver address to use
Address string
Address string
goresolver *net.Resolver
sync.RWMutex
}
// Resolve assumes ID is a domain name e.g micro.mu
// Resolve tries to resolve endpoint address
func (r *Resolver) Resolve(name string) ([]*resolver.Record, error) {
host, port, err := net.SplitHostPort(name)
if err != nil {
@@ -28,56 +31,46 @@ func (r *Resolver) Resolve(name string) ([]*resolver.Record, error) {
}
if len(r.Address) == 0 {
r.Address = "1.0.0.1:53"
r.Address = "1.1.1.1:53"
}
//nolint:prealloc
var records []*resolver.Record
// parsed an actual ip
if v := net.ParseIP(host); v != nil {
records = append(records, &resolver.Record{
Address: net.JoinHostPort(host, port),
})
return records, nil
rec := &resolver.Record{Address: net.JoinHostPort(host, port)}
return []*resolver.Record{rec}, nil
}
for _, q := range []uint16{dns.TypeA, dns.TypeAAAA} {
m := new(dns.Msg)
m.SetQuestion(dns.Fqdn(host), q)
rec, err := dns.ExchangeContext(context.Background(), m, r.Address)
if err != nil {
return nil, err
}
r.RLock()
goresolver := r.goresolver
r.RUnlock()
var addr string
for _, answer := range rec.Answer {
h := answer.Header()
// check record type matches
switch h.Rrtype {
case dns.TypeA:
arec, _ := answer.(*dns.A)
addr = arec.A.String()
case dns.TypeAAAA:
arec, _ := answer.(*dns.AAAA)
addr = arec.AAAA.String()
default:
continue
}
// join resolved record with port
address := net.JoinHostPort(addr, port)
// append to record set
records = append(records, &resolver.Record{
Address: address,
})
if goresolver == nil {
r.Lock()
r.goresolver = &net.Resolver{
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
d := net.Dialer{
Timeout: time.Millisecond * time.Duration(100),
}
return d.DialContext(ctx, "udp", r.Address)
},
}
r.Unlock()
}
// no records returned so just best effort it
if len(records) == 0 {
addrs, err := goresolver.LookupIP(context.TODO(), "ip", host)
if err != nil {
return nil, err
}
if len(addrs) == 0 {
rec := &resolver.Record{Address: net.JoinHostPort(host, port)}
return []*resolver.Record{rec}, nil
}
records := make([]*resolver.Record, 0, len(addrs))
for _, addr := range addrs {
records = append(records, &resolver.Record{
Address: net.JoinHostPort(host, port),
Address: net.JoinHostPort(addr.String(), port),
})
}

14
resolver/dns/dns_test.go Normal file
View File

@@ -0,0 +1,14 @@
package dns
import "testing"
func TestResolver(t *testing.T) {
r := &Resolver{}
recs, err := r.Resolve("unistack.org")
if err != nil {
t.Fatal(err)
}
if len(recs) < 1 {
t.Fatalf("records not resolved: %v", recs)
}
}

View File

@@ -1,4 +1,4 @@
// Package dns srv resolves names to dns srv records
// Package dnssrv resolves names to dns srv records
package dnssrv
import (
@@ -9,7 +9,9 @@ import (
)
// Resolver is a DNS network resolve
type Resolver struct{}
type Resolver struct {
Address string
}
// Resolve assumes ID is a domain name e.g micro.mu
func (r *Resolver) Resolve(name string) ([]*resolver.Record, error) {

View File

@@ -4,31 +4,30 @@ package http
import (
"encoding/json"
"errors"
"io/ioutil"
"io"
"net/http"
"net/url"
"github.com/unistack-org/micro/v3/resolver"
)
// Resolver is a HTTP network resolver
type Resolver struct {
// If not set, defaults to http
// HTTPResolver is a HTTP network resolver
type HTTPResolver struct {
// Proto if not set, defaults to http
Proto string
// Path sets the path to lookup. Defaults to /network
Path string
// Host url to use for the query
Host string
}
// Response contains resolver.Record
type Response struct {
Nodes []*resolver.Record `json:"nodes,omitempty"`
}
// Resolve assumes ID is a domain which can be converted to a http://name/network request
func (r *Resolver) Resolve(name string) ([]*resolver.Record, error) {
func (r *HTTPResolver) Resolve(name string) ([]*resolver.Record, error) {
proto := "http"
host := "localhost:8080"
path := "/network/nodes"
@@ -62,7 +61,7 @@ func (r *Resolver) Resolve(name string) ([]*resolver.Record, error) {
if rsp.StatusCode != 200 {
return nil, errors.New("non 200 response")
}
b, err := ioutil.ReadAll(rsp.Body)
b, err := io.ReadAll(rsp.Body)
if err != nil {
return nil, err
}

View File

@@ -5,6 +5,7 @@ import (
"github.com/unistack-org/micro/v3/resolver"
)
// Resolver contains noop resolver
type Resolver struct{}
// Resolve returns the list of nodes

View File

@@ -36,7 +36,17 @@ type Route struct {
// Hash returns route hash sum.
func (r *Route) Hash() uint64 {
h := fnv.New64()
h.Reset()
h.Write([]byte(r.Service + r.Address + r.Gateway + r.Network + r.Router + r.Link))
//nolint:errcheck
h.Write([]byte(r.Service))
//nolint:errcheck
h.Write([]byte(r.Address))
//nolint:errcheck
h.Write([]byte(r.Gateway))
//nolint:errcheck
h.Write([]byte(r.Network))
//nolint:errcheck
h.Write([]byte(r.Router))
//nolint:errcheck
h.Write([]byte(r.Link))
return h.Sum64()
}

View File

@@ -8,8 +8,6 @@ import (
"github.com/unistack-org/micro/v3/logger"
)
type Option func(o *Options)
// Options configure runtime
type Options struct {
// Scheduler for updates
@@ -26,6 +24,9 @@ type Options struct {
Logger logger.Logger
}
// Option func signature
type Option func(o *Options)
// WithLogger sets the logger
func WithLogger(l logger.Logger) Option {
return func(o *Options) {
@@ -68,8 +69,10 @@ func WithClient(c client.Client) Option {
}
}
// CreateOption func signature
type CreateOption func(o *CreateOptions)
// ReadOption func signature
type ReadOption func(o *ReadOptions)
// CreateOptions configure runtime services
@@ -230,8 +233,10 @@ func ReadContext(ctx context.Context) ReadOption {
}
}
// UpdateOption func signature
type UpdateOption func(o *UpdateOptions)
// UpdateOptions struct
type UpdateOptions struct {
// Namespace the service is running in
Namespace string
@@ -241,7 +246,7 @@ type UpdateOptions struct {
Secrets map[string]string
}
// WithSecret sets a secret to provide the service with
// UpdateSecret sets a secret to provide the service with
func UpdateSecret(key, value string) UpdateOption {
return func(o *UpdateOptions) {
if o.Secrets == nil {
@@ -266,8 +271,10 @@ func UpdateContext(ctx context.Context) UpdateOption {
}
}
// DeleteOption func signature
type DeleteOption func(o *DeleteOptions)
// DeleteOptions struct
type DeleteOptions struct {
// Namespace the service is running in
Namespace string
@@ -304,7 +311,7 @@ type LogsOptions struct {
Context context.Context
}
// LogsExistingCount confiures how many existing lines to show
// LogsCount confiures how many existing lines to show
func LogsCount(count int64) LogsOption {
return func(l *LogsOptions) {
l.Count = count

View File

@@ -1,9 +1,8 @@
package random
import (
"math/rand"
"github.com/unistack-org/micro/v3/selector"
"github.com/unistack-org/micro/v3/util/rand"
)
type random struct{}
@@ -20,9 +19,9 @@ func (r *random) Select(routes []string, opts ...selector.SelectOption) (selecto
if len(routes) == 1 {
return routes[0]
}
var rng rand.Rand
// select a random route from the slice
return routes[rand.Intn(len(routes))]
return routes[rng.Intn(len(routes))]
}, nil
}
@@ -40,5 +39,5 @@ func (r *random) String() string {
// NewSelector returns a random selector
func NewSelector(opts ...selector.Option) selector.Selector {
return new(random)
return &random{}
}

View File

@@ -1,9 +1,8 @@
package roundrobin
import (
"math/rand"
"github.com/unistack-org/micro/v3/selector"
"github.com/unistack-org/micro/v3/util/rand"
)
// NewSelector returns an initialised round robin selector
@@ -18,8 +17,8 @@ func (r *roundrobin) Select(routes []string, opts ...selector.SelectOption) (sel
if len(routes) == 0 {
return nil, selector.ErrNoneAvailable
}
i := rand.Intn(len(routes))
var rng rand.Rand
i := rng.Intn(len(routes))
return func() string {
route := routes[i%len(routes)]

View File

@@ -3,7 +3,6 @@ package roundrobin
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/unistack-org/micro/v3/selector"
)
@@ -19,18 +18,29 @@ func TestRoundRobin(t *testing.T) {
// By passing r1 and r2 first, it forces a set sequence of (r1 => r2 => r3 => r1)
next, err := sel.Select([]string{r1})
if err != nil {
t.Fatal(err)
}
r := next()
assert.Nil(t, err, "Error should be nil")
assert.Equal(t, r1, r, "Expected route to be r1")
if r1 != r {
t.Fatal("Expected route to be r == r1")
}
next, err = sel.Select([]string{r2})
if err != nil {
t.Fatal(err)
}
r = next()
assert.Nil(t, err, "Error should be nil")
assert.Equal(t, r2, r, "Expected route to be r2")
if r2 != r {
t.Fatal("Expected route to be r2")
}
routes := []string{r1, r2, r3}
next, err = sel.Select(routes)
assert.Nil(t, err, "Error should be nil")
if err != nil {
t.Fatal(err)
}
n1, n2, n3, n4 := next(), next(), next(), next()
// start element is random but then it should loop through in order
@@ -41,9 +51,19 @@ func TestRoundRobin(t *testing.T) {
break
}
}
assert.NotEqual(t, start, -1)
assert.Equal(t, routes[start], n1, "Unexpected route")
assert.Equal(t, routes[(start+1)%3], n2, "Unexpected route")
assert.Equal(t, routes[(start+2)%3], n3, "Unexpected route")
assert.Equal(t, routes[(start+3)%3], n4, "Unexpected route")
if start == -1 {
t.Fatalf("start == -1 %v %v", start, -1)
}
if routes[start] != n1 {
t.Fatal("Unexpected route")
}
if routes[(start+1)%3] != n2 {
t.Fatal("Unexpected route")
}
if routes[(start+2)%3] != n3 {
t.Fatal("Unexpected route")
}
if routes[(start+3)%3] != n4 {
t.Fatal("Unexpected route")
}
}

View File

@@ -2,8 +2,6 @@ package selector
import (
"testing"
"github.com/stretchr/testify/assert"
)
// Tests runs all the tests against a selector to ensure the implementations are consistent
@@ -14,19 +12,27 @@ func Tests(t *testing.T, s Selector) {
t.Run("Select", func(t *testing.T) {
t.Run("NoRoutes", func(t *testing.T) {
_, err := s.Select([]string{})
assert.Equal(t, ErrNoneAvailable, err, "Expected error to be none available")
if err != ErrNoneAvailable {
t.Fatal("Expected error to be none available")
}
})
t.Run("OneRoute", func(t *testing.T) {
next, err := s.Select([]string{r1})
if err != nil {
t.Fatal("Error should be nil")
}
srv := next()
assert.Nil(t, err, "Error should be nil")
assert.Equal(t, r1, srv, "Expected the route to be returned")
if r1 != srv {
t.Fatal("Expected the route to be returned")
}
})
t.Run("MultipleRoutes", func(t *testing.T) {
next, err := s.Select([]string{r1, r2})
assert.Nil(t, err, "Error should be nil")
if err != nil {
t.Fatal("Error should be nil")
}
srv := next()
if srv != r1 && srv != r2 {
t.Errorf("Expected the route to be one of the inputs")
@@ -35,11 +41,14 @@ func Tests(t *testing.T, s Selector) {
})
t.Run("Record", func(t *testing.T) {
err := s.Record(r1, nil)
assert.Nil(t, err, "Expected the error to be nil")
if err := s.Record(r1, nil); err != nil {
t.Fatal("Expected the error to be nil")
}
})
t.Run("String", func(t *testing.T) {
assert.NotEmpty(t, s.String(), "String returned a blank string")
if s.String() == "" {
t.Fatal("String returned a blank string")
}
})
}

59
server/errors.go Normal file
View File

@@ -0,0 +1,59 @@
package server
import "github.com/unistack-org/micro/v3/errors"
type Error struct {
id string
}
func NewError(id string) *Error {
return &Error{id}
}
func (e *Error) BadRequest(format string, a ...interface{}) error {
return errors.BadRequest(e.id, format, a...)
}
func (e *Error) Unauthorized(format string, a ...interface{}) error {
return errors.Unauthorized(e.id, format, a...)
}
func (e *Error) Forbidden(format string, a ...interface{}) error {
return errors.Forbidden(e.id, format, a...)
}
func (e *Error) NotFound(format string, a ...interface{}) error {
return errors.NotFound(e.id, format, a...)
}
func (e *Error) MethodNotAllowed(format string, a ...interface{}) error {
return errors.MethodNotAllowed(e.id, format, a...)
}
func (e *Error) Timeout(format string, a ...interface{}) error {
return errors.Timeout(e.id, format, a...)
}
func (e *Error) Conflict(format string, a ...interface{}) error {
return errors.Conflict(e.id, format, a...)
}
func (e *Error) InternalServerError(format string, a ...interface{}) error {
return errors.InternalServerError(e.id, format, a...)
}
func (e *Error) NotImplemented(format string, a ...interface{}) error {
return errors.NotImplemented(e.id, format, a...)
}
func (e *Error) BadGateway(format string, a ...interface{}) error {
return errors.BadGateway(e.id, format, a...)
}
func (e *Error) ServiceUnavailable(format string, a ...interface{}) error {
return errors.ServiceUnavailable(e.id, format, a...)
}
func (e *Error) GatewayTimeout(format string, a ...interface{}) error {
return errors.GatewayTimeout(e.id, format, a...)
}

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