Bump reva deps (#8412)

* bump dependencies

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

* bump reva and add config options

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

---------

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
This commit is contained in:
Jörn Friedrich Dreyer
2024-02-21 10:20:36 +01:00
committed by GitHub
parent c92ebf4b46
commit 5ed57cc09a
490 changed files with 19128 additions and 11161 deletions
+5
View File
@@ -1,6 +1,7 @@
# Develop tools
/.vscode/
/.idea/
/.trunk
# Binaries for programs and plugins
*.exe
@@ -34,3 +35,7 @@ _cgo_export.*
*~
*.swp
*.swo
# go work files
go.work
go.work.sum
+251
View File
@@ -0,0 +1,251 @@
# This file contains all available configuration options
# with their default values.
# options for analysis running
run:
# go: '1.18'
# default concurrency is a available CPU number
# concurrency: 4
# timeout for analysis, e.g. 30s, 5m, default is 1m
deadline: 10m
# exit code when at least one issue was found, default is 1
issues-exit-code: 1
# include test files or not, default is true
tests: true
# which files to skip: they will be analyzed, but issues from them
# won't be reported. Default value is empty list, but there is
# no need to include all autogenerated files, we confidently recognize
# autogenerated files. If it's not please let us know.
skip-files:
[]
# - .*\\.pb\\.go$
allow-parallel-runners: true
# list of build tags, all linters use it. Default is empty list.
build-tags: []
# output configuration options
output:
# Format: colored-line-number|line-number|json|tab|checkstyle|code-climate|junit-xml|github-actions
#
# Multiple can be specified by separating them by comma, output can be provided
# for each of them by separating format name and path by colon symbol.
# Output path can be either `stdout`, `stderr` or path to the file to write to.
# Example: "checkstyle:report.json,colored-line-number"
#
# Default: colored-line-number
format: colored-line-number
# Print lines of code with issue.
# Default: true
print-issued-lines: true
# Print linter name in the end of issue text.
# Default: true
print-linter-name: true
# Make issues output unique by line.
# Default: true
uniq-by-line: true
# Add a prefix to the output file references.
# Default is no prefix.
path-prefix: ""
# Sort results by: filepath, line and column.
sort-results: true
# all available settings of specific linters
linters-settings:
wsl:
allow-cuddle-with-calls: ["Lock", "RLock", "defer"]
funlen:
lines: 80
statements: 60
varnamelen:
# The longest distance, in source lines, that is being considered a "small scope".
# Variables used in at most this many lines will be ignored.
# Default: 5
max-distance: 26
ignore-names:
- err
- id
- ch
- wg
- mu
ignore-decls:
- c echo.Context
- t testing.T
- f *foo.Bar
- e error
- i int
- const C
- T any
- m map[string]int
errcheck:
# report about not checking of errors in type assetions: `a := b.(MyStruct)`;
# default is false: such cases aren't reported by default.
check-type-assertions: true
# report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`;
# default is false: such cases aren't reported by default.
check-blank: true
govet:
# report about shadowed variables
check-shadowing: false
gofmt:
# simplify code: gofmt with `-s` option, true by default
simplify: true
gocyclo:
# minimal code complexity to report, 30 by default (but we recommend 10-20)
min-complexity: 15
maligned:
# print struct with more effective memory layout or not, false by default
suggest-new: true
dupl:
# tokens count to trigger issue, 150 by default
threshold: 100
goconst:
# minimal length of string constant, 3 by default
min-len: 3
# minimal occurrences count to trigger, 3 by default
min-occurrences: 3
depguard:
list-type: blacklist
# Packages listed here will reported as error if imported
packages:
- github.com/golang/protobuf/proto
misspell:
# Correct spellings using locale preferences for US or UK.
# Default is to use a neutral variety of English.
# Setting locale to US will correct the British spelling of 'colour' to 'color'.
locale: US
lll:
# max line length, lines longer will be reported. Default is 120.
# '\t' is counted as 1 character by default, and can be changed with the tab-width option
line-length: 120
# tab width in spaces. Default to 1.
tab-width: 1
unused:
# treat code as a program (not a library) and report unused exported identifiers; default is false.
# XXX: if you enable this setting, unused will report a lot of false-positives in text editors:
# if it's called for subdir of a project it can't find funcs usages. All text editor integrations
# with golangci-lint call it on a directory with the changed file.
check-exported: false
unparam:
# call graph construction algorithm (cha, rta). In general, use cha for libraries,
# and rta for programs with main packages. Default is cha.
algo: cha
# Inspect exported functions, default is false. Set to true if no external program/library imports your code.
# XXX: if you enable this setting, unparam will report a lot of false-positives in text editors:
# if it's called for subdir of a project it can't find external interfaces. All text editor integrations
# with golangci-lint call it on a directory with the changed file.
check-exported: false
nakedret:
# make an issue if func has more lines of code than this setting and it has naked returns; default is 30
max-func-lines: 60
nolintlint:
allow-unused: false
allow-leading-space: false
allow-no-explanation: []
require-explanation: false
require-specific: true
prealloc:
# XXX: we don't recommend using this linter before doing performance profiling.
# For most programs usage of prealloc will be a premature optimization.
# Report preallocation suggestions only on simple loops that have no returns/breaks/continues/gotos in them.
# True by default.
simple: true
range-loops: true # Report preallocation suggestions on range loops, true by default
for-loops: false # Report preallocation suggestions on for loops, false by default
cyclop:
# the maximal code complexity to report
max-complexity: 20
gomoddirectives:
replace-local: true
retract-allow-no-explanation: false
exclude-forbidden: true
linters:
enable-all: true
disable-all: false
fast: false
disable:
- golint
- varcheck
- ifshort
- structcheck
- deadcode
# - nosnakecase
- interfacer
- maligned
- scopelint
- exhaustivestruct
- testpackage
- promlinter
- nonamedreturns
- makezero
- gofumpt
- nlreturn
- thelper
# Can be considered to be enabled
- gochecknoinits
- gochecknoglobals # RIP
- dogsled
- wrapcheck
- paralleltest
- ireturn
- gomnd
- goerr113
- exhaustruct
- containedctx
- godox
- forcetypeassert
- gci
- lll
issues:
# List of regexps of issue texts to exclude, empty list by default.
# But independently from this option we use default exclude patterns,
# it can be disabled by `exclude-use-default: false`. To list all
# excluded by default patterns execute `golangci-lint run --help`
# exclude:
# - package comment should be of the form "Package services ..." # revive
# - ^ST1000 # ST1000: at least one file in a package should have a package comment (stylecheck)
# exclude-rules:
# - path: internal/app/machined/pkg/system/services
# linters:
# - dupl
exclude-rules:
- path: _test\.go
linters:
- gocyclo
- dupl
- gosec
- funlen
- varnamelen
- wsl
# Independently from option `exclude` we use default exclude patterns,
# it can be disabled by this option. To list all
# excluded by default patterns execute `golangci-lint run --help`.
# Default value for this option is true.
exclude-use-default: false
# Maximum issues count per one linter. Set to 0 to disable. Default is 50.
max-issues-per-linter: 0
# Maximum count of issues with the same text. Set to 0 to disable. Default is 3.
max-same-issues: 0
# Show only new issues: if there are unstaged changes or untracked files,
# only those changes are analyzed, else only changes in HEAD~ are analyzed.
# It's a super-useful option for integration of golangci-lint into existing
# large codebase. It's not practical to fix all existing issues at the moment
# of integration: much better don't allow issues in new code.
# Default is false.
new: false
+29
View File
@@ -0,0 +1,29 @@
labelType: long
coverThreshold: 70
buildStyle:
bold: true
foreground: yellow
startStyle:
foreground: lightBlack
passStyle:
foreground: green
failStyle:
bold: true
foreground: "#821515"
skipStyle:
foreground: lightBlack
passPackageStyle:
foreground: green
hide: false
failPackageStyle:
bold: true
foreground: "#821515"
coveredStyle:
foreground: green
uncoveredStyle:
bold: true
foreground: yellow
fileStyle:
foreground: cyan
lineStyle:
foreground: magenta
+48 -4
View File
@@ -1,8 +1,9 @@
# Go Micro [![License](https://img.shields.io/:license-apache-blue.svg)](https://opensource.org/licenses/Apache-2.0) [![Go.Dev reference](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/go-micro.dev/v4?tab=doc) [![Go Report Card](https://goreportcard.com/badge/github.com/go-micro/go-micro)](https://goreportcard.com/report/github.com/go-micro/go-micro) [![](https://dcbadge.vercel.app/api/server/qV3HvnEJfB?style=flat-square&theme=default-inverted)](https://discord.gg/qV3HvnEJfB)
# Go Micro [![License](https://img.shields.io/:license-apache-blue.svg)](https://opensource.org/licenses/Apache-2.0) [![Go.Dev reference](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/go-micro.dev/v4?tab=doc) [![Go Report Card](https://goreportcard.com/badge/github.com/go-micro/go-micro)](https://goreportcard.com/report/github.com/go-micro/go-micro) [![Discord](https://dcbadge.vercel.app/api/server/qV3HvnEJfB?style=flat-square&theme=default-inverted)](https://discord.gg/qV3HvnEJfB)
Go Micro is a framework for distributed systems development.
Note: V5 is in development. Leave feedback in this [form](https://forms.gle/41gEWAcSgnf88GKt7)
## Overview
Go Micro provides the core requirements for distributed systems development including RPC and Event driven communication.
@@ -52,14 +53,38 @@ Go Micro abstracts away the details of distributed systems. Here are the main fe
## Getting Started
To make use of Go Micro
To make use of Go Micro import it
```golang
import "go-micro.dev/v4"
import "go-micro.dev/v4
```
Define a handler (protobuf is optionally supported - see [example](https://github.com/go-micro/examples/blob/main/helloworld/main.go))
```golang
type Request struct {
Name string `json:"name"`
}
type Response struct {
Message string `json:"message"`
}
type Helloworld struct{}
func (h *Helloworld) Greeting(ctx context.Context, req *Request, rsp *Response) error {
rsp.Message = "Hello " + req.Name
return nil
}
```
Create, initialise and run the service
```golang
// create a new service
service := micro.NewService(
micro.Name("helloworld"),
micro.Handle(new(Helloworld)),
)
// initialise flags
@@ -69,6 +94,25 @@ service.Init()
service.Run()
```
Optionally set fixed address
```golang
service := micro.NewService(
// set address
micro.Handle(":8080"),
)
```
Call it via curl
```
curl -XPOST \
-H 'Content-Type: application/json' \
-H 'Micro-Endpoint: Helloworld.Greeting' \
-d '{"name": "alice"}' \
http://localhost:8080
```
See the [examples](https://github.com/go-micro/examples) for detailed information on usage.
## Toolkit
+20 -14
View File
@@ -8,14 +8,15 @@ import (
"strings"
"go-micro.dev/v4/api/router"
"go-micro.dev/v4/client"
"go-micro.dev/v4/registry"
"go-micro.dev/v4/server"
)
// The gateway interface provides a way to
// create composable API gateways
// API interface provides a way to
// create composable API gateways.
type Api interface {
// Initialise options
// Initialize options
Init(...Option) error
// Get the options
Options() Options
@@ -29,16 +30,20 @@ type Api interface {
String() string
}
// Options are API options.
type Options struct {
// Address of the server
Address string
// Router for resolving routes
Router router.Router
// Client to use for RPC
Client client.Client
// Address of the server
Address string
}
// Option type are API option args.
type Option func(*Options) error
// Endpoint is a mapping between an RPC method and HTTP endpoint
// Endpoint is a mapping between an RPC method and HTTP endpoint.
type Endpoint struct {
// RPC Method e.g. Greeter.Hello
Name string
@@ -56,7 +61,7 @@ type Endpoint struct {
Stream bool
}
// Service represents an API service
// Service represents an API service.
type Service struct {
// Name of service
Name string
@@ -82,21 +87,22 @@ func slice(s string) []string {
return sl
}
// Encode encodes an endpoint to endpoint metadata
// Encode encodes an endpoint to endpoint metadata.
func Encode(e *Endpoint) map[string]string {
if e == nil {
return nil
}
// endpoint map
ep := make(map[string]string)
em := make(map[string]string)
// set vals only if they exist
set := func(k, v string) {
if len(v) == 0 {
return
}
ep[k] = v
em[k] = v
}
set("endpoint", e.Name)
@@ -106,10 +112,10 @@ func Encode(e *Endpoint) map[string]string {
set("path", strings.Join(e.Path, ","))
set("host", strings.Join(e.Host, ","))
return ep
return em
}
// Decode decodes endpoint metadata into an endpoint
// Decode decodes endpoint metadata into an endpoint.
func Decode(e map[string]string) *Endpoint {
if e == nil {
return nil
@@ -125,7 +131,7 @@ func Decode(e map[string]string) *Endpoint {
}
}
// Validate validates an endpoint to guarantee it won't blow up when being served
// Validate validates an endpoint to guarantee it won't blow up when being served.
func Validate(e *Endpoint) error {
if e == nil {
return errors.New("endpoint is nil")
@@ -172,7 +178,7 @@ func WithEndpoint(e *Endpoint) server.HandlerOption {
return server.EndpointMetadata(e.Name, Encode(e))
}
// NewApi returns a new api gateway
// NewApi returns a new api gateway.
func NewApi(opts ...Option) Api {
return newApi(opts...)
}
+3 -4
View File
@@ -45,7 +45,6 @@ func newApi(opts ...Option) Api {
}
}
// Initialise options
func (a *api) Init(opts ...Option) error {
for _, o := range opts {
o(&a.options)
@@ -53,17 +52,17 @@ func (a *api) Init(opts ...Option) error {
return nil
}
// Get the options
// Get the options.
func (a *api) Options() Options {
return a.options
}
// Register a http handler
// Register a http handler.
func (a *api) Register(*Endpoint) error {
return nil
}
// Register a route
// Register a route.
func (a *api) Deregister(*Endpoint) error {
return nil
}
+1 -1
View File
@@ -5,7 +5,7 @@ import (
"net/http"
)
// Handler represents a HTTP handler that manages a request
// Handler represents a HTTP handler that manages a request.
type Handler interface {
// standard http handler
http.Handler
+12 -8
View File
@@ -7,20 +7,23 @@ import (
)
var (
DefaultMaxRecvSize int64 = 1024 * 1024 * 100 // 10Mb
// DefaultMaxRecvSize is 10MiB.
DefaultMaxRecvSize int64 = 1024 * 1024 * 100
)
// Options is the list of api Options.
type Options struct {
MaxRecvSize int64
Namespace string
Router router.Router
Client client.Client
Logger logger.Logger
Namespace string
MaxRecvSize int64
}
// Option is a api Option.
type Option func(o *Options)
// NewOptions fills in the blanks
// NewOptions fills in the blanks.
func NewOptions(opts ...Option) Options {
options := Options{
Logger: logger.DefaultLogger,
@@ -45,34 +48,35 @@ func NewOptions(opts ...Option) Options {
return options
}
// WithNamespace specifies the namespace for the handler
// WithNamespace specifies the namespace for the handler.
func WithNamespace(s string) Option {
return func(o *Options) {
o.Namespace = s
}
}
// WithRouter specifies a router to be used by the handler
// WithRouter specifies a router to be used by the handler.
func WithRouter(r router.Router) Option {
return func(o *Options) {
o.Router = r
}
}
// WithClient sets the client for the handler.
func WithClient(c client.Client) Option {
return func(o *Options) {
o.Client = c
}
}
// WithMaxRecvSize specifies max body size
// WithMaxRecvSize specifies max body size.
func WithMaxRecvSize(size int64) Option {
return func(o *Options) {
o.MaxRecvSize = size
}
}
// WithLogger specifies the logger
// WithLogger specifies the logger.
func WithLogger(l logger.Logger) Option {
return func(o *Options) {
o.Logger = l
+88 -50
View File
@@ -11,7 +11,6 @@ import (
jsonpatch "github.com/evanphx/json-patch/v5"
"github.com/oxtoacart/bpool"
"go-micro.dev/v4/api/handler"
"go-micro.dev/v4/api/internal/proto"
"go-micro.dev/v4/api/router"
@@ -29,19 +28,20 @@ import (
)
const (
// Handler is the name of this handler.
Handler = "rpc"
packageID = "go.micro.api"
)
var (
// supported json codecs
// supported json codecs.
jsonCodecs = []string{
"application/grpc+json",
"application/json",
"application/json-rpc",
}
// support proto codecs
// support proto codecs.
protoCodecs = []string{
"application/grpc",
"application/grpc+proto",
@@ -66,7 +66,7 @@ func (b *buffer) Write(_ []byte) (int, error) {
return 0, nil
}
// strategy is a hack for selection
// strategy is a hack for selection.
func strategy(services []*registry.Service) selector.Strategy {
return func(_ []*registry.Service) selector.Next {
// ignore input to this function, use services above
@@ -77,6 +77,7 @@ func strategy(services []*registry.Service) selector.Strategy {
func (h *rpcHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
logger := h.opts.Logger
bsize := handler.DefaultMaxRecvSize
if h.opts.MaxRecvSize > 0 {
bsize = h.opts.MaxRecvSize
}
@@ -84,6 +85,7 @@ func (h *rpcHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, bsize)
defer r.Body.Close()
var service *router.Route
if h.opts.Router != nil {
@@ -94,8 +96,10 @@ func (h *rpcHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if werr != nil {
logger.Log(log.ErrorLevel, werr)
}
return
}
service = s
} else {
// we have no way of routing the request
@@ -106,18 +110,18 @@ func (h *rpcHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}
ct := r.Header.Get("Content-Type")
contentType := r.Header.Get("Content-Type")
// Strip charset from Content-Type (like `application/json; charset=UTF-8`)
if idx := strings.IndexRune(ct, ';'); idx >= 0 {
ct = ct[:idx]
if idx := strings.IndexRune(contentType, ';'); idx >= 0 {
contentType = contentType[:idx]
}
// micro client
c := h.opts.Client
myClient := h.opts.Client
// create context
cx := ctx.FromRequest(r)
myContext := ctx.FromRequest(r)
// get context from http handler wrappers
md, ok := metadata.FromContext(r.Context())
if !ok {
@@ -133,23 +137,24 @@ func (h *rpcHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
// merge context with overwrite
cx = metadata.MergeContext(cx, md, true)
myContext = metadata.MergeContext(myContext, md, true)
// set merged context to request
*r = *r.Clone(cx)
*r = *r.Clone(myContext)
// if stream we currently only support json
if isStream(r, service) {
// drop older context as it can have timeouts and create new
// md, _ := metadata.FromContext(cx)
//serveWebsocket(context.TODO(), w, r, service, c)
if err := serveWebsocket(cx, w, r, service, c); err != nil {
// serveWebsocket(context.TODO(), w, r, service, c)
if err := serveWebsocket(myContext, w, r, service, myClient); err != nil {
logger.Log(log.ErrorLevel, err)
}
return
}
// create strategy
so := selector.WithStrategy(strategy(service.Versions))
mySelector := selector.WithStrategy(strategy(service.Versions))
// walk the standard call path
// get payload
@@ -158,6 +163,7 @@ func (h *rpcHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if werr := writeError(w, r, err); werr != nil {
logger.Log(log.ErrorLevel, werr)
}
return
}
@@ -165,7 +171,7 @@ func (h *rpcHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
switch {
// proto codecs
case hasCodec(ct, protoCodecs):
case hasCodec(contentType, protoCodecs):
request := &proto.Message{}
// if the extracted payload isn't empty lets use it
if len(br) > 0 {
@@ -175,18 +181,19 @@ func (h *rpcHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// create request/response
response := &proto.Message{}
req := c.NewRequest(
req := myClient.NewRequest(
service.Service,
service.Endpoint.Name,
request,
client.WithContentType(ct),
client.WithContentType(contentType),
)
// make the call
if err := c.Call(cx, req, response, client.WithSelectOption(so)); err != nil {
if err := myClient.Call(myContext, req, response, client.WithSelectOption(mySelector)); err != nil {
if werr := writeError(w, r, err); werr != nil {
logger.Log(log.ErrorLevel, werr)
}
return
}
@@ -196,13 +203,14 @@ func (h *rpcHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if werr := writeError(w, r, err); werr != nil {
logger.Log(log.ErrorLevel, werr)
}
return
}
default:
// if json codec is not present set to json
if !hasCodec(ct, jsonCodecs) {
ct = "application/json"
if !hasCodec(contentType, jsonCodecs) {
contentType = "application/json"
}
// default to trying json
@@ -215,17 +223,18 @@ func (h *rpcHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// create request/response
var response json.RawMessage
req := c.NewRequest(
req := myClient.NewRequest(
service.Service,
service.Endpoint.Name,
&request,
client.WithContentType(ct),
client.WithContentType(contentType),
)
// make the call
if err := c.Call(cx, req, &response, client.WithSelectOption(so)); err != nil {
if err := myClient.Call(myContext, req, &response, client.WithSelectOption(mySelector)); err != nil {
if werr := writeError(w, r, err); werr != nil {
logger.Log(log.ErrorLevel, werr)
}
return
}
@@ -235,6 +244,7 @@ func (h *rpcHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if werr := writeError(w, r, err); werr != nil {
logger.Log(log.ErrorLevel, werr)
}
return
}
}
@@ -245,8 +255,8 @@ func (h *rpcHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
}
func (rh *rpcHandler) String() string {
return "rpc"
func (h *rpcHandler) String() string {
return Handler
}
func hasCodec(ct string, codecs []string) bool {
@@ -255,49 +265,57 @@ func hasCodec(ct string, codecs []string) bool {
return true
}
}
return false
}
// requestPayload takes a *http.Request.
// If the request is a GET the query string parameters are extracted and marshaled to JSON and the raw bytes are returned.
// If the request method is a POST the request body is read and returned
// If the request method is a POST the request body is read and returned.
func requestPayload(r *http.Request) ([]byte, error) {
var err error
// we have to decode json-rpc and proto-rpc because we suck
// well actually because there's no proxy codec right now
ct := r.Header.Get("Content-Type")
myCt := r.Header.Get("Content-Type")
switch {
case strings.Contains(ct, "application/json-rpc"):
case strings.Contains(myCt, "application/json-rpc"):
msg := codec.Message{
Type: codec.Request,
Header: make(map[string]string),
}
c := jsonrpc.NewCodec(&buffer{r.Body})
if err = c.ReadHeader(&msg, codec.Request); err != nil {
return nil, err
}
var raw json.RawMessage
if err = c.ReadBody(&raw); err != nil {
return nil, err
}
return ([]byte)(raw), nil
case strings.Contains(ct, "application/proto-rpc"), strings.Contains(ct, "application/octet-stream"):
case strings.Contains(myCt, "application/proto-rpc"), strings.Contains(myCt, "application/octet-stream"):
msg := codec.Message{
Type: codec.Request,
Header: make(map[string]string),
}
c := protorpc.NewCodec(&buffer{r.Body})
if err = c.ReadHeader(&msg, codec.Request); err != nil {
return nil, err
}
var raw proto.Message
if err = c.ReadBody(&raw); err != nil {
return nil, err
}
return raw.Marshal()
case strings.Contains(ct, "application/www-x-form-urlencoded"):
case strings.Contains(myCt, "application/www-x-form-urlencoded"), strings.Contains(myCt, "application/x-www-form-urlencoded"):
if err := r.ParseForm(); err != nil {
return nil, err
}
@@ -315,6 +333,7 @@ func requestPayload(r *http.Request) ([]byte, error) {
// otherwise as per usual
ctx := r.Context()
// dont user meadata.FromContext as it mangles names
md, ok := metadata.FromContext(ctx)
if !ok {
@@ -331,9 +350,11 @@ func requestPayload(r *http.Request) ([]byte, error) {
// filter own keys
if strings.HasPrefix(k, "x-api-field-") {
matches[strings.TrimPrefix(k, "x-api-field-")] = v
delete(md, k)
} else if k == "x-api-body" {
bodydst = v
delete(md, k)
}
}
@@ -344,10 +365,12 @@ func requestPayload(r *http.Request) ([]byte, error) {
// get fields from url values
if len(r.URL.RawQuery) > 0 {
umd := make(map[string]interface{})
err = qson.Unmarshal(&umd, r.URL.RawQuery)
if err != nil {
return nil, err
}
for k, v := range umd {
matches[k] = v
}
@@ -362,24 +385,29 @@ func requestPayload(r *http.Request) ([]byte, error) {
req[k] = v
continue
}
em := make(map[string]interface{})
em[ps[len(ps)-1]] = v
for i := len(ps) - 2; i > 0; i-- {
nm := make(map[string]interface{})
nm[ps[i]] = em
em = nm
}
if vm, ok := req[ps[0]]; ok {
// nested map
nm := vm.(map[string]interface{})
for vk, vv := range em {
nm[vk] = vv
}
req[ps[0]] = nm
} else {
req[ps[0]] = em
}
}
pathbuf := []byte("{}")
if len(req) > 0 {
pathbuf, err = json.Marshal(req)
@@ -389,48 +417,55 @@ func requestPayload(r *http.Request) ([]byte, error) {
}
urlbuf := []byte("{}")
out, err := jsonpatch.MergeMergePatches(urlbuf, pathbuf)
if err != nil {
return nil, err
}
switch r.Method {
case "GET":
case http.MethodGet:
// empty response
if strings.Contains(ct, "application/json") && string(out) == "{}" {
if strings.Contains(myCt, "application/json") && string(out) == "{}" {
return out, nil
} else if string(out) == "{}" && !strings.Contains(ct, "application/json") {
} else if string(out) == "{}" && !strings.Contains(myCt, "application/json") {
return []byte{}, nil
}
return out, nil
case "PATCH", "POST", "PUT", "DELETE":
case http.MethodPatch, http.MethodPost, http.MethodPut, http.MethodDelete:
bodybuf := []byte("{}")
buf := bufferPool.Get()
defer bufferPool.Put(buf)
if _, err := buf.ReadFrom(r.Body); err != nil {
return nil, err
}
if b := buf.Bytes(); len(b) > 0 {
bodybuf = b
}
if bodydst == "" || bodydst == "*" {
if out, err = jsonpatch.MergeMergePatches(out, bodybuf); err == nil {
return out, nil
}
}
var jsonbody map[string]interface{}
if json.Valid(bodybuf) {
if err = json.Unmarshal(bodybuf, &jsonbody); err != nil {
return nil, err
}
}
dstmap := make(map[string]interface{})
ps := strings.Split(bodydst, ".")
if len(ps) == 1 {
if jsonbody != nil {
dstmap[ps[0]] = jsonbody
} else {
// old unexpected behaviour
// old unexpected behavior
dstmap[ps[0]] = bodybuf
}
} else {
@@ -438,7 +473,7 @@ func requestPayload(r *http.Request) ([]byte, error) {
if jsonbody != nil {
em[ps[len(ps)-1]] = jsonbody
} else {
// old unexpected behaviour
// old unexpected behavior
em[ps[len(ps)-1]] = bodybuf
}
for i := len(ps) - 2; i > 0; i-- {
@@ -458,41 +493,41 @@ func requestPayload(r *http.Request) ([]byte, error) {
return out, nil
}
//fallback to previous unknown behaviour
return bodybuf, nil
}
return []byte{}, nil
}
func writeError(w http.ResponseWriter, r *http.Request, err error) error {
func writeError(rsp http.ResponseWriter, req *http.Request, err error) error {
ce := errors.Parse(err.Error())
switch ce.Code {
case 0:
// assuming it's totally screwed
ce.Code = 500
ce.Code = http.StatusInternalServerError
ce.Id = packageID
ce.Status = http.StatusText(500)
ce.Status = http.StatusText(http.StatusInternalServerError)
ce.Detail = "error during request: " + ce.Detail
w.WriteHeader(500)
rsp.WriteHeader(http.StatusInternalServerError)
default:
w.WriteHeader(int(ce.Code))
rsp.WriteHeader(int(ce.Code))
}
// response content type
w.Header().Set("Content-Type", "application/json")
rsp.Header().Set("Content-Type", "application/json")
// Set trailers
if strings.Contains(r.Header.Get("Content-Type"), "application/grpc") {
w.Header().Set("Trailer", "grpc-status")
w.Header().Set("Trailer", "grpc-message")
w.Header().Set("grpc-status", "13")
w.Header().Set("grpc-message", ce.Detail)
if strings.Contains(req.Header.Get("Content-Type"), "application/grpc") {
rsp.Header().Set("Trailer", "grpc-status")
rsp.Header().Set("Trailer", "grpc-message")
rsp.Header().Set("grpc-status", "13")
rsp.Header().Set("grpc-message", ce.Detail)
}
_, werr := w.Write([]byte(ce.Error()))
_, werr := rsp.Write([]byte(ce.Error()))
return werr
}
@@ -515,11 +550,14 @@ func writeResponse(w http.ResponseWriter, r *http.Request, rsp []byte) error {
// write response
_, err := w.Write(rsp)
return err
}
// NewHandler returns a new RPC handler.
func NewHandler(opts ...handler.Option) handler.Handler {
options := handler.NewOptions(opts...)
return &rpcHandler{
opts: options,
}
+35 -23
View File
@@ -12,41 +12,41 @@ import (
"github.com/gobwas/httphead"
"github.com/gobwas/ws"
"github.com/gobwas/ws/wsutil"
"go-micro.dev/v4/api/router"
"go-micro.dev/v4/client"
raw "go-micro.dev/v4/codec/bytes"
"go-micro.dev/v4/selector"
)
// serveWebsocket will stream rpc back over websockets assuming json
// serveWebsocket will stream rpc back over websockets assuming json.
func serveWebsocket(ctx context.Context, w http.ResponseWriter, r *http.Request, service *router.Route, c client.Client) (err error) {
var op ws.OpCode
var opCode ws.OpCode
ct := r.Header.Get("Content-Type")
myCt := r.Header.Get("Content-Type")
// Strip charset from Content-Type (like `application/json; charset=UTF-8`)
if idx := strings.IndexRune(ct, ';'); idx >= 0 {
ct = ct[:idx]
if idx := strings.IndexRune(myCt, ';'); idx >= 0 {
myCt = myCt[:idx]
}
// check proto from request
switch ct {
switch myCt {
case "application/json":
op = ws.OpText
opCode = ws.OpText
default:
op = ws.OpBinary
opCode = ws.OpBinary
}
hdr := make(http.Header)
if proto, ok := r.Header["Sec-Websocket-Protocol"]; ok {
for _, p := range proto {
switch p {
case "binary":
if p == "binary" {
hdr["Sec-WebSocket-Protocol"] = []string{"binary"}
op = ws.OpBinary
opCode = ws.OpBinary
}
}
}
payload, err := requestPayload(r)
if err != nil {
return
@@ -67,7 +67,7 @@ func serveWebsocket(ctx context.Context, w http.ResponseWriter, r *http.Request,
Header: hdr,
}
conn, rw, _, err := upgrader.Upgrade(r, w)
conn, uRw, _, err := upgrader.Upgrade(r, w)
if err != nil {
return
}
@@ -79,8 +79,9 @@ func serveWebsocket(ctx context.Context, w http.ResponseWriter, r *http.Request,
}()
var request interface{}
if !bytes.Equal(payload, []byte(`{}`)) {
switch ct {
switch myCt {
case "application/json", "":
m := json.RawMessage(payload)
request = &m
@@ -90,20 +91,25 @@ func serveWebsocket(ctx context.Context, w http.ResponseWriter, r *http.Request,
}
// we always need to set content type for message
if ct == "" {
ct = "application/json"
if myCt == "" {
myCt = "application/json"
}
req := c.NewRequest(
service.Service,
service.Endpoint.Name,
request,
client.WithContentType(ct),
client.WithContentType(myCt),
client.StreamingRequest(),
)
cCtx, cancel := context.WithCancel(ctx)
defer cancel()
so := selector.WithStrategy(strategy(service.Versions))
// create a new stream
stream, err := c.Stream(ctx, req, client.WithSelectOption(so))
stream, err := c.Stream(cCtx, req, client.WithSelectOption(so))
if err != nil {
return
}
@@ -115,7 +121,7 @@ func serveWebsocket(ctx context.Context, w http.ResponseWriter, r *http.Request,
}
go func() {
if wErr := writeLoop(rw, stream); wErr != nil && err == nil {
if wErr := writeLoop(uRw, stream); wErr != nil && err == nil {
err = wErr
}
}()
@@ -137,21 +143,23 @@ func serveWebsocket(ctx context.Context, w http.ResponseWriter, r *http.Request,
if strings.Contains(err.Error(), "context canceled") {
return nil
}
return err
}
// write the response
if err = wsutil.WriteServerMessage(rw, op, buf); err != nil {
if err = wsutil.WriteServerMessage(uRw, opCode, buf); err != nil {
return err
}
if err = rw.Flush(); err != nil {
if err = uRw.Flush(); err != nil {
return err
}
}
}
}
// writeLoop
// writeLoop.
func writeLoop(rw io.ReadWriter, stream client.Stream) error {
// close stream when done
defer stream.Close()
@@ -171,10 +179,12 @@ func writeLoop(rw io.ReadWriter, stream client.Stream) error {
case ws.StatusNormalClosure, ws.StatusNoStatusRcvd:
// this happens when user close ws connection, or we don't get any status
return nil
default:
return err
}
}
return err
}
switch op {
default:
// not relevant
@@ -211,6 +221,7 @@ func isStream(r *http.Request, srv *router.Route) bool {
}
}
}
return false
}
@@ -222,6 +233,7 @@ func isWebSocket(r *http.Request) bool {
return true
}
}
return false
}
+13 -1
View File
@@ -2,6 +2,9 @@ package api
import (
"go-micro.dev/v4/api/router"
registry2 "go-micro.dev/v4/api/router/registry"
"go-micro.dev/v4/client"
"go-micro.dev/v4/registry"
)
func NewOptions(opts ...Option) Options {
@@ -16,10 +19,19 @@ func NewOptions(opts ...Option) Options {
return options
}
// WithRouter sets the router to use e.g static or registry
// WithRouter sets the router to use e.g static or registry.
func WithRouter(r router.Router) Option {
return func(o *Options) error {
o.Router = r
return nil
}
}
// WithRegistry sets the api's client and router to use registry.
func WithRegistry(r registry.Registry) Option {
return func(o *Options) error {
o.Client = client.NewClient(client.Registry(r))
o.Router = registry2.NewRouter(router.WithRegistry(r))
return nil
}
}
+3 -3
View File
@@ -4,7 +4,7 @@ import (
"net/http"
)
// NewOptions returns new initialised options
// NewOptions wires options together.
func NewOptions(opts ...Option) Options {
var options Options
for _, o := range opts {
@@ -18,14 +18,14 @@ func NewOptions(opts ...Option) Options {
return options
}
// WithHandler sets the handler being used
// WithHandler sets the handler being used.
func WithHandler(h string) Option {
return func(o *Options) {
o.Handler = h
}
}
// WithNamespace sets the function which determines the namespace for a request
// WithNamespace sets the function which determines the namespace for a request.
func WithNamespace(n func(*http.Request) string) Option {
return func(o *Options) {
o.Namespace = n
+6 -4
View File
@@ -11,13 +11,13 @@ var (
ErrInvalidPath = errors.New("invalid path")
)
// Resolver resolves requests to endpoints
// Resolver resolves requests to endpoints.
type Resolver interface {
Resolve(r *http.Request) (*Endpoint, error)
String() string
}
// Endpoint is the endpoint for a http request
// Endpoint is the endpoint for a http request.
type Endpoint struct {
// e.g greeter
Name string
@@ -29,14 +29,16 @@ type Endpoint struct {
Path string
}
// Options is a struct of available options.
type Options struct {
Handler string
Namespace func(*http.Request) string
Handler string
}
// Option is a helper for a single option.
type Option func(o *Options)
// StaticNamespace returns the same namespace for each request
// StaticNamespace returns the same namespace for each request.
func StaticNamespace(ns string) func(*http.Request) string {
return func(*http.Request) string {
return ns
+4 -1
View File
@@ -1,4 +1,4 @@
// Package vpath resolves using http path and recognised versioned urls
// Package vpath resolves using http path and recognized versioned urls
package vpath
import (
@@ -10,10 +10,12 @@ import (
"go-micro.dev/v4/api/resolver"
)
// NewResolver returns a new vpath resolver.
func NewResolver(opts ...resolver.Option) resolver.Resolver {
return &Resolver{opts: resolver.NewOptions(opts...)}
}
// Resolver is a vpath resolver.
type Resolver struct {
opts resolver.Options
}
@@ -22,6 +24,7 @@ var (
re = regexp.MustCompile("^v[0-9]+$")
)
// Resolve resolves a http.Request to an grpc Endpoint.
func (r *Resolver) Resolve(req *http.Request) (*resolver.Endpoint, error) {
if req.URL.Path == "/" {
return nil, errors.New("unknown name")
+3 -3
View File
@@ -22,7 +22,7 @@ func slice(s string) []string {
return sl
}
// Encode encodes an endpoint to endpoint metadata
// Encode encodes an endpoint to endpoint metadata.
func Encode(e *Endpoint) map[string]string {
if e == nil {
return nil
@@ -49,7 +49,7 @@ func Encode(e *Endpoint) map[string]string {
return ep
}
// Decode decodes endpoint metadata into an endpoint
// Decode decodes endpoint metadata into an endpoint.
func Decode(e map[string]string) *Endpoint {
if e == nil {
return nil
@@ -65,7 +65,7 @@ func Decode(e map[string]string) *Endpoint {
}
}
// Validate validates an endpoint to guarantee it won't blow up when being served
// Validate validates an endpoint to guarantee it won't blow up when being served.
func Validate(e *Endpoint) error {
if e == nil {
return errors.New("endpoint is nil")
+5 -2
View File
@@ -7,15 +7,18 @@ import (
"go-micro.dev/v4/registry"
)
// Options is a struct of options available.
type Options struct {
Handler string
Registry registry.Registry
Resolver resolver.Resolver
Logger logger.Logger
Handler string
}
// Option is a helper for a single options.
type Option func(o *Options)
// NewOptions wires options together.
func NewOptions(opts ...Option) Options {
options := Options{
Handler: "meta",
@@ -54,7 +57,7 @@ func WithResolver(r resolver.Resolver) Option {
}
}
// WithLogger sets the underline logger
// WithLogger sets the underline logger.
func WithLogger(l logger.Logger) Option {
return func(o *Options) {
o.Logger = l
+62 -21
View File
@@ -18,25 +18,26 @@ import (
"go-micro.dev/v4/registry/cache"
)
// endpoint struct, that holds compiled pcre
// endpoint struct, that holds compiled pcre.
type endpoint struct {
hostregs []*regexp.Regexp
pathregs []util.Pattern
pcreregs []*regexp.Regexp
}
// router is the default router
// router is the default router.
type registryRouter struct {
exit chan bool
opts router.Options
// registry cache
rc cache.Cache
sync.RWMutex
eps map[string]*router.Route
exit chan bool
eps map[string]*router.Route
// compiled regexp for host and path
ceps map[string]*endpoint
sync.RWMutex
}
func (r *registryRouter) isStopped() bool {
@@ -48,17 +49,20 @@ func (r *registryRouter) isStopped() bool {
}
}
// refresh list of api services
// refresh list of api services.
func (r *registryRouter) refresh() {
var attempts int
logger := r.Options().Logger
for {
services, err := r.opts.Registry.ListServices()
if err != nil {
attempts++
logger.Logf(log.ErrorLevel, "unable to list services: %v", err)
time.Sleep(time.Duration(attempts) * time.Second)
continue
}
@@ -71,6 +75,7 @@ func (r *registryRouter) refresh() {
logger.Logf(log.ErrorLevel, "unable to get service: %v", err)
continue
}
r.store(service)
}
@@ -84,7 +89,7 @@ func (r *registryRouter) refresh() {
}
}
// process watch event
// process watch event.
func (r *registryRouter) process(res *registry.Result) {
logger := r.Options().Logger
// skip these things
@@ -103,7 +108,7 @@ func (r *registryRouter) process(res *registry.Result) {
r.store(service)
}
// store local endpoint cache
// store local endpoint cache.
func (r *registryRouter) store(services []*registry.Service) {
logger := r.Options().Logger
// endpoints
@@ -169,11 +174,13 @@ func (r *registryRouter) store(services []*registry.Service) {
if h == "" || h == "*" {
continue
}
hostreg, err := regexp.CompilePOSIX(h)
if err != nil {
logger.Logf(log.TraceLevel, "endpoint have invalid host regexp: %v", err)
continue
}
cep.hostregs = append(cep.hostregs, hostreg)
}
@@ -197,11 +204,13 @@ func (r *registryRouter) store(services []*registry.Service) {
}
tpl := rule.Compile()
pathreg, err := util.NewPattern(tpl.Version, tpl.OpCodes, tpl.Pool, "", util.PatternLogger(logger))
if err != nil {
logger.Logf(log.TraceLevel, "endpoint have invalid path pattern: %v", err)
continue
}
cep.pathregs = append(cep.pathregs, pathreg)
}
@@ -209,9 +218,10 @@ func (r *registryRouter) store(services []*registry.Service) {
}
}
// watch for endpoint changes
// watch for endpoint changes.
func (r *registryRouter) watch() {
var attempts int
logger := r.Options().Logger
for {
@@ -223,8 +233,10 @@ func (r *registryRouter) watch() {
w, err := r.opts.Registry.Watch()
if err != nil {
attempts++
logger.Logf(log.ErrorLevel, "error watching endpoints: %v", err)
time.Sleep(time.Duration(attempts) * time.Second)
continue
}
@@ -248,8 +260,10 @@ func (r *registryRouter) watch() {
if err != nil {
logger.Logf(log.ErrorLevel, "error getting next endoint: %v", err)
close(ch)
break
}
r.process(res)
}
}
@@ -267,6 +281,7 @@ func (r *registryRouter) Stop() error {
close(r.exit)
r.rc.Stop()
}
return nil
}
@@ -280,6 +295,7 @@ func (r *registryRouter) Deregister(ep *router.Route) error {
func (r *registryRouter) Endpoint(req *http.Request) (*router.Route, error) {
logger := r.Options().Logger
if r.isStopped() {
return nil, errors.New("router closed")
}
@@ -291,16 +307,19 @@ func (r *registryRouter) Endpoint(req *http.Request) (*router.Route, error) {
if len(req.URL.Path) > 0 && req.URL.Path != "/" {
idx = 1
}
path := strings.Split(req.URL.Path[idx:], "/")
// use the first match
// TODO: weighted matching
for n, e := range r.eps {
for n, endpoint := range r.eps {
cep, ok := r.ceps[n]
if !ok {
continue
}
ep := e.Endpoint
ep := endpoint.Endpoint
var mMatch, hMatch, pMatch bool
// 1. try method
for _, m := range ep.Method {
@@ -309,6 +328,7 @@ func (r *registryRouter) Endpoint(req *http.Request) (*router.Route, error) {
break
}
}
if !mMatch {
continue
}
@@ -323,14 +343,13 @@ func (r *registryRouter) Endpoint(req *http.Request) (*router.Route, error) {
if h == "" || h == "*" {
hMatch = true
break
} else {
if cep.hostregs[idx].MatchString(req.URL.Host) {
hMatch = true
break
}
} else if cep.hostregs[idx].MatchString(req.URL.Host) {
hMatch = true
break
}
}
}
if !hMatch {
continue
}
@@ -344,17 +363,23 @@ func (r *registryRouter) Endpoint(req *http.Request) (*router.Route, error) {
logger.Logf(log.DebugLevel, "api gpath not match %s != %v", path, pathreg)
continue
}
logger.Logf(log.DebugLevel, "api gpath match %s = %v", path, pathreg)
pMatch = true
ctx := req.Context()
md, ok := metadata.FromContext(ctx)
if !ok {
md = make(metadata.Metadata)
}
for k, v := range matches {
md[fmt.Sprintf("x-api-field-%s", k)] = v
}
*req = *req.Clone(metadata.NewContext(ctx, md))
break
}
@@ -365,8 +390,11 @@ func (r *registryRouter) Endpoint(req *http.Request) (*router.Route, error) {
logger.Logf(log.DebugLevel, "api pcre path not match %s != %v", path, pathreg)
continue
}
logger.Logf(log.DebugLevel, "api pcre path match %s != %v", path, pathreg)
pMatch = true
break
}
}
@@ -377,7 +405,7 @@ func (r *registryRouter) Endpoint(req *http.Request) (*router.Route, error) {
// TODO: Percentage traffic
// we got here, so its a match
return e, nil
return endpoint, nil
}
// no match
@@ -400,13 +428,13 @@ func (r *registryRouter) Route(req *http.Request) (*router.Route, error) {
// TODO: don't ignore that shit
// get the service name
rp, err := r.opts.Resolver.Resolve(req)
rsp, err := r.opts.Resolver.Resolve(req)
if err != nil {
return nil, err
}
// service name
name := rp.Name
name := rsp.Name
// get service
services, err := r.rc.GetService(name)
@@ -425,11 +453,22 @@ func (r *registryRouter) Route(req *http.Request) (*router.Route, error) {
handler = "rpc"
}
// extract endpoint from Path, case-sensitive
// just test it in this case, maybe should put the code somewhere else
ep_name := rsp.Method
comps := strings.Split(rsp.Path, "/")
switch len(comps) {
case 3:
ep_name = comps[1] + "." + comps[2]
case 4:
ep_name = comps[2] + "." + comps[3]
}
// construct api service
return &router.Route{
Service: name,
Endpoint: &router.Endpoint{
Name: rp.Method,
Name: ep_name,
Handler: handler,
},
Versions: services,
@@ -462,12 +501,14 @@ func newRouter(opts ...router.Option) *registryRouter {
eps: make(map[string]*router.Route),
ceps: make(map[string]*endpoint),
}
go r.watch()
go r.refresh()
return r
}
// NewRouter returns the default router
// NewRouter returns the default router.
func NewRouter(opts ...router.Option) router.Router {
return newRouter(opts...)
}
+2 -2
View File
@@ -7,7 +7,7 @@ import (
"go-micro.dev/v4/registry"
)
// Router is used to determine an endpoint for a request
// Router is used to determine an endpoint for a request.
type Router interface {
// Returns options
Options() Options
@@ -30,7 +30,7 @@ type Route struct {
Versions []*registry.Service
}
// Endpoint is a mapping between an RPC method and HTTP endpoint
// Endpoint is a mapping between an RPC method and HTTP endpoint.
type Endpoint struct {
// RPC Method e.g. Greeter.Hello
Name string
+16 -9
View File
@@ -1,6 +1,7 @@
package util
// download from https://raw.githubusercontent.com/grpc-ecosystem/grpc-gateway/master/protoc-gen-grpc-gateway/httprule/compile.go
// download from
// https://raw.githubusercontent.com/grpc-ecosystem/grpc-gateway/master/protoc-gen-grpc-gateway/httprule/compile.go
const (
opcodeVersion = 1
@@ -8,18 +9,18 @@ const (
// Template is a compiled representation of path templates.
type Template struct {
// Version is the version number of the format.
Version int
// Verb is a VERB part in the template.
Verb string
// Original template (example: /v1/a_bit_of_everything)
Template string
// OpCodes is a sequence of operations.
OpCodes []int
// Pool is a constant pool
Pool []string
// Verb is a VERB part in the template.
Verb string
// Fields is a list of field paths bound in this template.
Fields []string
// Original template (example: /v1/a_bit_of_everything)
Template string
// Version is the version number of the format.
Version int
}
// Compiler compiles utilities representation of path templates into marshallable operations.
@@ -29,13 +30,14 @@ type Compiler interface {
}
type op struct {
// code is the opcode of the operation
code OpCode
// str is a string operand of the code.
// operand is ignored if str is not empty.
str string
// code is the opcode of the operation
code OpCode
// operand is a numeric operand of the code.
operand int
}
@@ -66,6 +68,7 @@ func (v variable) compile() []op {
for _, s := range v.segments {
ops = append(ops, s.compile()...)
}
ops = append(ops, op{
code: OpConcatN,
operand: len(v.segments),
@@ -88,7 +91,9 @@ func (t template) Compile() Template {
pool []string
fields []string
)
consts := make(map[string]int)
for _, op := range rawOps {
ops = append(ops, int(op.code))
if op.str == "" {
@@ -100,10 +105,12 @@ func (t template) Compile() Template {
}
ops = append(ops, consts[op.str])
}
if op.code == OpCapture {
fields = append(fields, op.str)
}
}
return Template{
Version: opcodeVersion,
OpCodes: ops,
+42 -6
View File
@@ -1,6 +1,7 @@
package util
// download from https://raw.githubusercontent.com/grpc-ecosystem/grpc-gateway/master/protoc-gen-grpc-gateway/httprule/parse.go
// download from
// https://raw.githubusercontent.com/grpc-ecosystem/grpc-gateway/master/protoc-gen-grpc-gateway/httprule/parse.go
import (
"fmt"
@@ -19,14 +20,15 @@ func (e InvalidTemplateError) Error() string {
return fmt.Sprintf("%s: %s", e.msg, e.tmpl)
}
// Parse parses the string representation of path template
// Parse parses the string representation of path template.
func Parse(tmpl string) (Compiler, error) {
if !strings.HasPrefix(tmpl, "/") {
return template{}, InvalidTemplateError{tmpl: tmpl, msg: "no leading /"}
}
tokens, verb := tokenize(tmpl[1:])
tokens, verb := tokenize(tmpl[1:])
p := parser{tokens: tokens}
segs, err := p.topLevelSegments()
if err != nil {
return template{}, InvalidTemplateError{tmpl: tmpl, msg: err.Error()}
@@ -52,8 +54,10 @@ func tokenize(path string) (tokens []string, verb string) {
var (
st = init
)
for path != "" {
var idx int
switch st {
case init:
idx = strings.IndexAny(path, "/{")
@@ -62,10 +66,12 @@ func tokenize(path string) (tokens []string, verb string) {
case nested:
idx = strings.IndexAny(path, "/}")
}
if idx < 0 {
tokens = append(tokens, path)
break
}
switch r := path[idx]; r {
case '/', '.':
case '{':
@@ -75,30 +81,35 @@ func tokenize(path string) (tokens []string, verb string) {
case '}':
st = init
}
if idx == 0 {
tokens = append(tokens, path[idx:idx+1])
} else {
tokens = append(tokens, path[:idx], path[idx:idx+1])
}
path = path[idx+1:]
}
l := len(tokens)
t := tokens[l-1]
if idx := strings.LastIndex(t, ":"); idx == 0 {
tokens, verb = tokens[:l-1], t[1:]
} else if idx > 0 {
tokens[l-1], verb = t[:idx], t[idx+1:]
}
tokens = append(tokens, eof)
return tokens, verb
}
// parser is a parser of the template syntax defined in github.com/googleapis/googleapis/google/api/http.proto.
type parser struct {
logger log.Logger
tokens []string
accepted []string
logger log.Logger
}
// topLevelSegments is the target of this parser.
@@ -111,17 +122,22 @@ func (p *parser) topLevelSegments() ([]segment, error) {
if err != nil {
return nil, err
}
logger.Logf(log.DebugLevel, "accept segments: %q; %q", p.accepted, p.tokens)
if _, err := p.accept(typeEOF); err != nil {
return nil, fmt.Errorf("unexpected token %q after segments %q", p.tokens[0], strings.Join(p.accepted, ""))
}
logger.Logf(log.DebugLevel, "accept eof: %q; %q", p.accepted, p.tokens)
return segs, nil
}
func (p *parser) segments() ([]segment, error) {
logger := log.LoggerOrDefault(p.logger)
s, err := p.segment()
if err != nil {
return nil, err
}
@@ -133,11 +149,14 @@ func (p *parser) segments() ([]segment, error) {
if _, err := p.accept("/"); err != nil {
return segs, nil
}
s, err := p.segment()
if err != nil {
return segs, err
}
segs = append(segs, s)
logger.Logf(log.DebugLevel, "accept segment: %q; %q", p.accepted, p.tokens)
}
}
@@ -146,17 +165,20 @@ func (p *parser) segment() (segment, error) {
if _, err := p.accept("*"); err == nil {
return wildcard{}, nil
}
if _, err := p.accept("**"); err == nil {
return deepWildcard{}, nil
}
if l, err := p.literal(); err == nil {
return l, nil
}
v, err := p.variable()
if err != nil {
return nil, fmt.Errorf("segment neither wildcards, literal or variable: %v", err)
return nil, fmt.Errorf("segment neither wildcards, literal or variable: %w", err)
}
return v, err
}
@@ -165,6 +187,7 @@ func (p *parser) literal() (segment, error) {
if err != nil {
return nil, err
}
return literal(lit), nil
}
@@ -182,7 +205,7 @@ func (p *parser) variable() (segment, error) {
if _, err := p.accept("="); err == nil {
segs, err = p.segments()
if err != nil {
return nil, fmt.Errorf("invalid segment in variable %q: %v", path, err)
return nil, fmt.Errorf("invalid segment in variable %q: %w", path, err)
}
} else {
segs = []segment{wildcard{}}
@@ -191,6 +214,7 @@ func (p *parser) variable() (segment, error) {
if _, err := p.accept("}"); err != nil {
return nil, fmt.Errorf("unterminated variable segment: %s", path)
}
return variable{
path: path,
segments: segs,
@@ -202,7 +226,9 @@ func (p *parser) fieldPath() (string, error) {
if err != nil {
return "", err
}
components := []string{c}
for {
if _, err = p.accept("."); err != nil {
return strings.Join(components, "."), nil
@@ -238,6 +264,7 @@ const (
// If it doesn't match, the function does not consume any tokens and return an error.
func (p *parser) accept(term termType) (string, error) {
t := p.tokens[0]
switch term {
case "/", "*", "**", ".", "=", "{", "}":
if t != string(term) && t != "/" {
@@ -258,8 +285,10 @@ func (p *parser) accept(term termType) (string, error) {
default:
return "", fmt.Errorf("unknown termType %q", term)
}
p.tokens = p.tokens[1:]
p.accepted = append(p.accepted, t)
return t, nil
}
@@ -278,6 +307,7 @@ func expectPChars(t string) error {
pct1
pct2
)
st := init
for _, r := range t {
if st != init {
@@ -316,9 +346,11 @@ func expectPChars(t string) error {
return fmt.Errorf("invalid character in path segment: %q(%U)", r, r)
}
}
if st != init {
return fmt.Errorf("invalid percent-encoding in %q", t)
}
return nil
}
@@ -327,12 +359,14 @@ func expectIdent(ident string) error {
if ident == "" {
return fmt.Errorf("empty identifier")
}
for pos, r := range ident {
switch {
case '0' <= r && r <= '9':
if pos == 0 {
return fmt.Errorf("identifier starting with digit: %s", ident)
}
continue
case 'A' <= r && r <= 'Z':
continue
@@ -344,6 +378,7 @@ func expectIdent(ident string) error {
return fmt.Errorf("invalid character %q(%U) in identifier: %s", r, r, ident)
}
}
return nil
}
@@ -356,5 +391,6 @@ func isHexDigit(r rune) bool {
case 'a' <= r && r <= 'f':
return true
}
return false
}
+6 -6
View File
@@ -7,17 +7,17 @@ type OpCode int
// These constants are the valid values of OpCode.
const (
// OpNop does nothing
// OpNop does nothing.
OpNop = OpCode(iota)
// OpPush pushes a component to stack
// OpPush pushes a component to stack.
OpPush
// OpLitPush pushes a component to stack if it matches to the literal
// OpLitPush pushes a component to stack if it matches to the literal.
OpLitPush
// OpPushM concatenates the remaining components and pushes it to stack
// OpPushM concatenates the remaining components and pushes it to stack.
OpPushM
// OpConcatN pops N items from stack, concatenates them and pushes it back to stack
// OpConcatN pops N items from stack, concatenates them and pushes it back to stack.
OpConcatN
// OpCapture pops an item and binds it to the variable
// OpCapture pops an item and binds it to the variable.
OpCapture
// OpEnd is the least positive invalid opcode.
OpEnd
+13 -4
View File
@@ -24,6 +24,8 @@ type rop struct {
// Pattern is a template pattern of http request paths defined in github.com/googleapis/googleapis/google/api/http.proto.
type Pattern struct {
// verb is the VERB part of the path pattern. It is empty if the pattern does not have VERB part.
verb string
// ops is a list of operations
ops []rop
// pool is a constant pool indexed by the operands or vars.
@@ -34,22 +36,20 @@ type Pattern struct {
stacksize int
// tailLen is the length of the fixed-size segments after a deep wildcard
tailLen int
// verb is the VERB part of the path pattern. It is empty if the pattern does not have VERB part.
verb string
// assumeColonVerb indicates whether a path suffix after a final
// colon may only be interpreted as a verb.
assumeColonVerb bool
}
type patternOptions struct {
assumeColonVerb bool
logger log.Logger
assumeColonVerb bool
}
// PatternOpt is an option for creating Patterns.
type PatternOpt func(*patternOptions)
// Logger sets the logger
// PatternLogger sets the logger.
func PatternLogger(l log.Logger) PatternOpt {
return func(po *patternOptions) {
po.logger = l
@@ -89,8 +89,10 @@ func NewPattern(version int, ops []int, pool []string, verb string, opts ...Patt
pushMSeen bool
vars []string
)
for i := 0; i < l; i += 2 {
op := rop{code: OpCode(ops[i]), operand: ops[i+1]}
switch op.code {
case OpNop:
continue
@@ -104,6 +106,7 @@ func NewPattern(version int, ops []int, pool []string, verb string, opts ...Patt
logger.Logf(log.DebugLevel, "pushM appears twice")
return Pattern{}, ErrInvalidPattern
}
pushMSeen = true
stack++
case OpLitPush:
@@ -111,6 +114,7 @@ func NewPattern(version int, ops []int, pool []string, verb string, opts ...Patt
logger.Logf(log.DebugLevel, "negative literal index: %d", op.operand)
return Pattern{}, ErrInvalidPattern
}
if pushMSeen {
tailLen++
}
@@ -120,6 +124,7 @@ func NewPattern(version int, ops []int, pool []string, verb string, opts ...Patt
logger.Logf(log.DebugLevel, "negative concat size: %d", op.operand)
return Pattern{}, ErrInvalidPattern
}
stack -= op.operand
if stack < 0 {
logger.Logf(log.DebugLevel, "stack underflow")
@@ -131,10 +136,12 @@ func NewPattern(version int, ops []int, pool []string, verb string, opts ...Patt
logger.Logf(log.DebugLevel, "variable name index out of bound: %d", op.operand)
return Pattern{}, ErrInvalidPattern
}
v := pool[op.operand]
op.operand = len(vars)
vars = append(vars, v)
stack--
if stack < 0 {
logger.Logf(log.DebugLevel, "stack underflow")
return Pattern{}, ErrInvalidPattern
@@ -147,8 +154,10 @@ func NewPattern(version int, ops []int, pool []string, verb string, opts ...Patt
if maxstack < stack {
maxstack = stack
}
typedOps = append(typedOps, op)
}
return Pattern{
ops: typedOps,
pool: pool,
+6 -2
View File
@@ -1,6 +1,7 @@
package util
// download from https://raw.githubusercontent.com/grpc-ecosystem/grpc-gateway/master/protoc-gen-grpc-gateway/httprule/types.go
// download from
// https://raw.githubusercontent.com/grpc-ecosystem/grpc-gateway/master/protoc-gen-grpc-gateway/httprule/types.go
import (
"fmt"
@@ -8,9 +9,9 @@ import (
)
type template struct {
segments []segment
verb string
template string
segments []segment
}
type segment interface {
@@ -46,6 +47,7 @@ func (v variable) String() string {
for _, s := range v.segments {
segs = append(segs, s.String())
}
return fmt.Sprintf("{%s=%s}", v.path, strings.Join(segs, "/"))
}
@@ -54,9 +56,11 @@ func (t template) String() string {
for _, s := range t.segments {
segs = append(segs, s.String())
}
str := strings.Join(segs, "/")
if t.verb != "" {
str = fmt.Sprintf("%s:%s", str, t.verb)
}
return "/" + str
}
+3 -3
View File
@@ -9,11 +9,11 @@ import (
var (
// ErrProviderNotImplemented can be returned when attempting to
// instantiate an unimplemented provider
// instantiate an unimplemented provider.
ErrProviderNotImplemented = errors.New("Provider not implemented")
)
// Provider is a ACME provider interface
// Provider is a ACME provider interface.
type Provider interface {
// Listen returns a new listener
Listen(...string) (net.Listener, error)
@@ -21,7 +21,7 @@ type Provider interface {
TLSConfig(...string) (*tls.Config, error)
}
// The Let's Encrypt ACME endpoints
// The Let's Encrypt ACME endpoints.
const (
LetsEncryptStagingCA = "https://acme-staging-v02.api.letsencrypt.org/directory"
LetsEncryptProductionCA = "https://acme-v02.api.letsencrypt.org/directory"
+14 -15
View File
@@ -2,26 +2,17 @@ package acme
import (
"github.com/go-acme/lego/v4/challenge"
"go-micro.dev/v4/logger"
)
// Option (or Options) are passed to New() to configure providers
// Option (or Options) are passed to New() to configure providers.
type Option func(o *Options)
// Options represents various options you can present to ACME providers
// Options represents various options you can present to ACME providers.
type Options struct {
// AcceptTLS must be set to true to indicate that you have read your
// provider's terms of service.
AcceptToS bool
// CA is the CA to use
CA string
// ChallengeProvider is a go-acme/lego challenge provider. Set this if you
// want to use DNS Challenges. Otherwise, tls-alpn-01 will be used
ChallengeProvider challenge.Provider
// Issue certificates for domains on demand. Otherwise, certs will be
// retrieved / issued on start-up.
OnDemand bool
// Cache is a storage interface. Most ACME libraries have an cache, but
// there's no defined interface, so if you consume this option
// sanity check it before using.
@@ -29,16 +20,24 @@ type Options struct {
// Logger is the underling logging framework
Logger logger.Logger
// CA is the CA to use
CA string
// AcceptTLS must be set to true to indicate that you have read your
// provider's terms of service.
AcceptToS bool
// Issue certificates for domains on demand. Otherwise, certs will be
// retrieved / issued on start-up.
OnDemand bool
}
// AcceptToS indicates whether you accept your CA's terms of service
// AcceptToS indicates whether you accept your CA's terms of service.
func AcceptToS(b bool) Option {
return func(o *Options) {
o.AcceptToS = b
}
}
// CA sets the CA of an acme.Options
// CA sets the CA of an acme.Options.
func CA(CA string) Option {
return func(o *Options) {
o.CA = CA
@@ -63,14 +62,14 @@ func OnDemand(b bool) Option {
// Cache provides a cache / storage interface to the underlying ACME library
// as there is no standard, this needs to be validated by the underlying
// implentation.
// implementation.
func Cache(c interface{}) Option {
return func(o *Options) {
o.Cache = c
}
}
// Logger sets the underline logger
// Logger sets the underline logger.
func Logger(l logger.Logger) Option {
return func(o *Options) {
o.Logger = l
+4 -4
View File
@@ -6,12 +6,12 @@ import (
type Config struct {
AllowOrigin string
AllowCredentials bool
AllowMethods string
AllowHeaders string
AllowCredentials bool
}
// CombinedCORSHandler wraps a server and provides CORS headers
// CombinedCORSHandler wraps a server and provides CORS headers.
func CombinedCORSHandler(h http.Handler, config *Config) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if config != nil {
@@ -25,7 +25,7 @@ func CombinedCORSHandler(h http.Handler, config *Config) http.Handler {
})
}
// SetHeaders sets the CORS headers
// SetHeaders sets the CORS headers.
func SetHeaders(w http.ResponseWriter, _ *http.Request, config *Config) {
set := func(w http.ResponseWriter, k, v string) {
if v := w.Header().Get(k); len(v) > 0 {
@@ -33,7 +33,7 @@ func SetHeaders(w http.ResponseWriter, _ *http.Request, config *Config) {
}
w.Header().Set(k, v)
}
//For forward-compatible code, default values may not be provided in the future
// For forward-compatible code, default values may not be provided in the future
if config.AllowCredentials {
set(w, "Access-Control-Allow-Credentials", "true")
} else {
+5 -5
View File
@@ -9,19 +9,19 @@ import (
"sync"
"github.com/gorilla/handlers"
"go-micro.dev/v4/api/server"
"go-micro.dev/v4/api/server/cors"
log "go-micro.dev/v4/logger"
)
type httpServer struct {
mux *http.ServeMux
opts server.Options
mtx sync.RWMutex
address string
mux *http.ServeMux
exit chan chan error
address string
mtx sync.RWMutex
}
func NewServer(address string, opts ...server.Option) server.Server {
@@ -94,7 +94,7 @@ func (s *httpServer) Start() error {
go func() {
if err := http.Serve(l, s.mux); err != nil {
// temporary fix
//logger.Log(log.FatalLevel, err)
// logger.Log(log.FatalLevel, err)
logger.Log(log.ErrorLevel, err)
}
}()
+10 -11
View File
@@ -4,26 +4,25 @@ import (
"crypto/tls"
"net/http"
"go-micro.dev/v4/api/server/cors"
"go-micro.dev/v4/logger"
"go-micro.dev/v4/api/resolver"
"go-micro.dev/v4/api/server/acme"
"go-micro.dev/v4/api/server/cors"
"go-micro.dev/v4/logger"
)
type Option func(o *Options)
type Options struct {
ACMEProvider acme.Provider
Resolver resolver.Resolver
Logger logger.Logger
CORSConfig *cors.Config
TLSConfig *tls.Config
ACMEHosts []string
Wrappers []Wrapper
EnableACME bool
EnableCORS bool
CORSConfig *cors.Config
ACMEProvider acme.Provider
EnableTLS bool
ACMEHosts []string
TLSConfig *tls.Config
Resolver resolver.Resolver
Wrappers []Wrapper
Logger logger.Logger
}
type Wrapper func(h http.Handler) http.Handler
@@ -94,7 +93,7 @@ func Resolver(r resolver.Resolver) Option {
}
}
// Logger sets the underline logging framework
// Logger sets the underline logging framework.
func Logger(l logger.Logger) Option {
return func(o *Options) {
o.Logger = l
+1 -1
View File
@@ -5,7 +5,7 @@ import (
"net/http"
)
// Server serves api requests
// Server serves api requests.
type Server interface {
Address() string
Init(opts ...Option) error
+27 -27
View File
@@ -8,22 +8,22 @@ import (
)
const (
// BearerScheme used for Authorization header
// BearerScheme used for Authorization header.
BearerScheme = "Bearer "
// ScopePublic is the scope applied to a rule to allow access to the public
// ScopePublic is the scope applied to a rule to allow access to the public.
ScopePublic = ""
// ScopeAccount is the scope applied to a rule to limit to users with any valid account
// ScopeAccount is the scope applied to a rule to limit to users with any valid account.
ScopeAccount = "*"
)
var (
// ErrInvalidToken is when the token provided is not valid
// ErrInvalidToken is when the token provided is not valid.
ErrInvalidToken = errors.New("invalid token provided")
// ErrForbidden is when a user does not have the necessary scope to access a resource
// ErrForbidden is when a user does not have the necessary scope to access a resource.
ErrForbidden = errors.New("resource forbidden")
)
// Auth provides authentication and authorization
// Auth provides authentication and authorization.
type Auth interface {
// Init the auth
Init(opts ...Option)
@@ -39,7 +39,7 @@ type Auth interface {
String() string
}
// Rules manages access to resources
// Rules manages access to resources.
type Rules interface {
// Verify an account has access to a resource using the rules
Verify(acc *Account, res *Resource, opts ...VerifyOption) error
@@ -51,40 +51,40 @@ type Rules interface {
List(...ListOption) ([]*Rule, error)
}
// Account provided by an auth provider
// Account provided by an auth provider.
type Account struct {
// Any other associated metadata
Metadata map[string]string `json:"metadata"`
// ID of the account e.g. email
ID string `json:"id"`
// Type of the account, e.g. service
Type string `json:"type"`
// Issuer of the account
Issuer string `json:"issuer"`
// Any other associated metadata
Metadata map[string]string `json:"metadata"`
// Scopes the account has access to
Scopes []string `json:"scopes"`
// Secret for the account, e.g. the password
Secret string `json:"secret"`
// Scopes the account has access to
Scopes []string `json:"scopes"`
}
// Token can be short or long lived
// Token can be short or long lived.
type Token struct {
// The token to be used for accessing resources
AccessToken string `json:"access_token"`
// RefreshToken to be used to generate a new token
RefreshToken string `json:"refresh_token"`
// Time of token creation
Created time.Time `json:"created"`
// Time of token expiry
Expiry time.Time `json:"expiry"`
// The token to be used for accessing resources
AccessToken string `json:"access_token"`
// RefreshToken to be used to generate a new token
RefreshToken string `json:"refresh_token"`
}
// Expired returns a boolean indicating if the token needs to be refreshed
// Expired returns a boolean indicating if the token needs to be refreshed.
func (t *Token) Expired() bool {
return t.Expiry.Unix() < time.Now().Unix()
}
// Resource is an entity such as a user or
// Resource is an entity such as a user or.
type Resource struct {
// Name of the resource, e.g. go.micro.service.notes
Name string `json:"name"`
@@ -94,25 +94,25 @@ type Resource struct {
Endpoint string `json:"endpoint"`
}
// Access defines the type of access a rule grants
// Access defines the type of access a rule grants.
type Access int
const (
// AccessGranted to a resource
// AccessGranted to a resource.
AccessGranted Access = iota
// AccessDenied to a resource
// AccessDenied to a resource.
AccessDenied
)
// Rule is used to verify access to a resource
// Rule is used to verify access to a resource.
type Rule struct {
// Resource the rule applies to
Resource *Resource
// ID of the rule, e.g. "public"
ID string
// Scope the rule requires, a blank scope indicates open to the public and * indicates the rule
// applies to any valid account
Scope string
// Resource the rule applies to
Resource *Resource
// Access determines if the rule grants or denies access to the resource
Access Access
// Priority the rule should take when verifying a request, the higher the value the sooner the
@@ -125,13 +125,13 @@ type accountKey struct{}
// AccountFromContext gets the account from the context, which
// is set by the auth wrapper at the start of a call. If the account
// is not set, a nil account will be returned. The error is only returned
// when there was a problem retrieving an account
// when there was a problem retrieving an account.
func AccountFromContext(ctx context.Context) (*Account, bool) {
acc, ok := ctx.Value(accountKey{}).(*Account)
return acc, ok
}
// ContextWithAccount sets the account in the context
// ContextWithAccount sets the account in the context.
func ContextWithAccount(ctx context.Context, account *Account) context.Context {
return context.WithValue(ctx, accountKey{}, account)
}
+9 -9
View File
@@ -30,24 +30,24 @@ type noop struct {
type noopRules struct{}
// String returns the name of the implementation
// String returns the name of the implementation.
func (n *noop) String() string {
return "noop"
}
// Init the auth
// Init the auth.
func (n *noop) Init(opts ...Option) {
for _, o := range opts {
o(&n.opts)
}
}
// Options set for auth
// Options set for auth.
func (n *noop) Options() Options {
return n.opts
}
// Generate a new account
// Generate a new account.
func (n *noop) Generate(id string, opts ...GenerateOption) (*Account, error) {
options := NewGenerateOptions(opts...)
@@ -60,18 +60,18 @@ func (n *noop) Generate(id string, opts ...GenerateOption) (*Account, error) {
}, nil
}
// Grant access to a resource
// Grant access to a resource.
func (n *noopRules) Grant(rule *Rule) error {
return nil
}
// Revoke access to a resource
// Revoke access to a resource.
func (n *noopRules) Revoke(rule *Rule) error {
return nil
}
// Rules used to verify requests
// Verify an account has access to a resource
// Verify an account has access to a resource.
func (n *noopRules) Verify(acc *Account, res *Resource, opts ...VerifyOption) error {
return nil
}
@@ -80,12 +80,12 @@ func (n *noopRules) List(opts ...ListOption) ([]*Rule, error) {
return []*Rule{}, nil
}
// Inspect a token
// Inspect a token.
func (n *noop) Inspect(token string) (*Account, error) {
return &Account{ID: uuid.New().String(), Issuer: n.Options().Namespace}, nil
}
// Token generation using an account id and secret
// Token generation using an account id and secret.
func (n *noop) Token(opts ...TokenOption) (*Token, error) {
return &Token{}, nil
}
+22 -22
View File
@@ -20,62 +20,62 @@ func NewOptions(opts ...Option) Options {
}
type Options struct {
// Logger is the underline logger
Logger logger.Logger
// Token is the services token used to authenticate itself
Token *Token
// Namespace the service belongs to
Namespace string
// ID is the services auth ID
ID string
// Secret is used to authenticate the service
Secret string
// Token is the services token used to authenticate itself
Token *Token
// PublicKey for decoding JWTs
PublicKey string
// PrivateKey for encoding JWTs
PrivateKey string
// Addrs sets the addresses of auth
Addrs []string
// Logger is the underline logger
Logger logger.Logger
}
type Option func(o *Options)
// Addrs is the auth addresses to use
// Addrs is the auth addresses to use.
func Addrs(addrs ...string) Option {
return func(o *Options) {
o.Addrs = addrs
}
}
// Namespace the service belongs to
// Namespace the service belongs to.
func Namespace(n string) Option {
return func(o *Options) {
o.Namespace = n
}
}
// PublicKey is the JWT public key
// PublicKey is the JWT public key.
func PublicKey(key string) Option {
return func(o *Options) {
o.PublicKey = key
}
}
// PrivateKey is the JWT private key
// PrivateKey is the JWT private key.
func PrivateKey(key string) Option {
return func(o *Options) {
o.PrivateKey = key
}
}
// WithLogger sets the underline logger
// WithLogger sets the underline logger.
func WithLogger(l logger.Logger) Option {
return func(o *Options) {
o.Logger = l
}
}
// Credentials sets the auth credentials
// Credentials sets the auth credentials.
func Credentials(id, secret string) Option {
return func(o *Options) {
o.ID = id
@@ -83,7 +83,7 @@ func Credentials(id, secret string) Option {
}
}
// ClientToken sets the auth token to use when making requests
// ClientToken sets the auth token to use when making requests.
func ClientToken(token *Token) Option {
return func(o *Options) {
o.Token = token
@@ -93,54 +93,54 @@ func ClientToken(token *Token) Option {
type GenerateOptions struct {
// Metadata associated with the account
Metadata map[string]string
// Scopes the account has access too
Scopes []string
// Provider of the account, e.g. oauth
Provider string
// Type of the account, e.g. user
Type string
// Secret used to authenticate the account
Secret string
// Scopes the account has access too
Scopes []string
}
type GenerateOption func(o *GenerateOptions)
// WithSecret for the generated account
// WithSecret for the generated account.
func WithSecret(s string) GenerateOption {
return func(o *GenerateOptions) {
o.Secret = s
}
}
// WithType for the generated account
// WithType for the generated account.
func WithType(t string) GenerateOption {
return func(o *GenerateOptions) {
o.Type = t
}
}
// WithMetadata for the generated account
// WithMetadata for the generated account.
func WithMetadata(md map[string]string) GenerateOption {
return func(o *GenerateOptions) {
o.Metadata = md
}
}
// WithProvider for the generated account
// WithProvider for the generated account.
func WithProvider(p string) GenerateOption {
return func(o *GenerateOptions) {
o.Provider = p
}
}
// WithScopes for the generated account
// WithScopes for the generated account.
func WithScopes(s ...string) GenerateOption {
return func(o *GenerateOptions) {
o.Scopes = s
}
}
// NewGenerateOptions from a slice of options
// NewGenerateOptions from a slice of options.
func NewGenerateOptions(opts ...GenerateOption) GenerateOptions {
var options GenerateOptions
for _, o := range opts {
@@ -162,7 +162,7 @@ type TokenOptions struct {
type TokenOption func(o *TokenOptions)
// WithExpiry for the token
// WithExpiry for the token.
func WithExpiry(ex time.Duration) TokenOption {
return func(o *TokenOptions) {
o.Expiry = ex
@@ -182,14 +182,14 @@ func WithToken(rt string) TokenOption {
}
}
// NewTokenOptions from a slice of options
// NewTokenOptions from a slice of options.
func NewTokenOptions(opts ...TokenOption) TokenOptions {
var options TokenOptions
for _, o := range opts {
o(&options)
}
// set defualt expiry of token
// set default expiry of token
if options.Expiry == 0 {
options.Expiry = time.Minute
}
+1 -1
View File
@@ -8,7 +8,7 @@ import (
// Verify 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
// will be returned.
func Verify(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}
+6 -3
View File
@@ -18,12 +18,13 @@ type Broker interface {
// message and optional Ack method to acknowledge receipt of the message.
type Handler func(Event) error
// Message is a message send/received from the broker.
type Message struct {
Header map[string]string
Body []byte
}
// Event is given to a subscription handler for processing
// Event is given to a subscription handler for processing.
type Event interface {
Topic() string
Message() *Message
@@ -31,7 +32,7 @@ type Event interface {
Error() error
}
// Subscriber is a convenience return type for the Subscribe method
// Subscriber is a convenience return type for the Subscribe method.
type Subscriber interface {
Options() SubscribeOptions
Topic() string
@@ -39,7 +40,8 @@ type Subscriber interface {
}
var (
DefaultBroker Broker = NewBroker()
// DefaultBroker is the default Broker.
DefaultBroker = NewBroker()
)
func Init(opts ...Option) error {
@@ -62,6 +64,7 @@ func Subscribe(topic string, handler Handler, opts ...SubscribeOption) (Subscrib
return DefaultBroker.Subscribe(topic, handler, opts...)
}
// String returns the name of the Broker.
func String() string {
return DefaultBroker.String()
}
+23 -21
View File
@@ -1,4 +1,4 @@
// Package http provides a http based message broker
// Package broker provides a http based message broker
package broker
import (
@@ -16,51 +16,53 @@ import (
"time"
"github.com/google/uuid"
"golang.org/x/net/http2"
"go-micro.dev/v4/codec/json"
merr "go-micro.dev/v4/errors"
"go-micro.dev/v4/registry"
"go-micro.dev/v4/registry/cache"
"go-micro.dev/v4/transport/headers"
maddr "go-micro.dev/v4/util/addr"
mnet "go-micro.dev/v4/util/net"
mls "go-micro.dev/v4/util/tls"
"golang.org/x/net/http2"
)
// HTTP Broker is a point to point async broker
// HTTP Broker is a point to point async broker.
type httpBroker struct {
id string
address string
opts Options
opts Options
r registry.Registry
mux *http.ServeMux
c *http.Client
r registry.Registry
sync.RWMutex
c *http.Client
subscribers map[string][]*httpSubscriber
running bool
exit chan chan error
inbox map[string][][]byte
id string
address string
sync.RWMutex
// offline message inbox
mtx sync.RWMutex
inbox map[string][][]byte
mtx sync.RWMutex
running bool
}
type httpSubscriber struct {
opts SubscribeOptions
id string
topic string
fn Handler
svc *registry.Service
hb *httpBroker
id string
topic string
}
type httpEvent struct {
err error
m *Message
t string
err error
}
var (
@@ -314,8 +316,8 @@ func (h *httpBroker) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
topic := m.Header["Micro-Topic"]
//delete(m.Header, ":topic")
topic := m.Header[headers.Message]
// delete(m.Header, ":topic")
if len(topic) == 0 {
errr := merr.InternalServerError("go.micro.broker", "Topic not found")
@@ -518,7 +520,7 @@ func (h *httpBroker) Publish(topic string, msg *Message, opts ...PublishOption)
m.Header[k] = v
}
m.Header["Micro-Topic"] = topic
m.Header[headers.Message] = topic
// encode the message
b, err := h.opts.Codec.Marshal(m)
@@ -703,7 +705,7 @@ func (h *httpBroker) String() string {
return "http"
}
// NewBroker returns a new http broker
// NewBroker returns a new http broker.
func NewBroker(opts ...Option) Broker {
return newHttpBroker(opts...)
}
+8 -8
View File
@@ -8,7 +8,6 @@ import (
"time"
"github.com/google/uuid"
log "go-micro.dev/v4/logger"
maddr "go-micro.dev/v4/util/addr"
mnet "go-micro.dev/v4/util/net"
@@ -17,25 +16,26 @@ import (
type memoryBroker struct {
opts *Options
Subscribers map[string][]*memorySubscriber
addr string
sync.RWMutex
connected bool
Subscribers map[string][]*memorySubscriber
connected bool
}
type memoryEvent struct {
opts *Options
topic string
err error
message interface{}
opts *Options
topic string
}
type memorySubscriber struct {
id string
topic string
opts SubscribeOptions
exit chan bool
handler Handler
opts SubscribeOptions
id string
topic string
}
func (m *memoryBroker) Options() Options {
+25 -23
View File
@@ -10,23 +10,24 @@ import (
)
type Options struct {
Addrs []string
Secure bool
Codec codec.Marshaler
Codec codec.Marshaler
// Logger is the underlying logger
Logger logger.Logger
// Registry used for clustering
Registry registry.Registry
// Other options for implementations of the interface
// can be stored in a context
Context context.Context
// Handler executed when error happens in broker mesage
// processing
ErrorHandler Handler
TLSConfig *tls.Config
// Registry used for clustering
Registry registry.Registry
// Other options for implementations of the interface
// can be stored in a context
Context context.Context
Addrs []string
Secure bool
}
type PublishOptions struct {
@@ -36,24 +37,25 @@ type PublishOptions struct {
}
type SubscribeOptions struct {
// AutoAck defaults to true. When a handler returns
// with a nil error the message is acked.
AutoAck bool
// Other options for implementations of the interface
// can be stored in a context
Context context.Context
// Subscribers with the same queue name
// will create a shared subscription where each
// receives a subset of messages.
Queue string
// Other options for implementations of the interface
// can be stored in a context
Context context.Context
// AutoAck defaults to true. When a handler returns
// with a nil error the message is acked.
AutoAck bool
}
type Option func(*Options)
type PublishOption func(*PublishOptions)
// PublishContext set context
// PublishContext set context.
func PublishContext(ctx context.Context) PublishOption {
return func(o *PublishOptions) {
o.Context = ctx
@@ -87,7 +89,7 @@ func NewSubscribeOptions(opts ...SubscribeOption) SubscribeOptions {
return opt
}
// Addrs sets the host addresses to be used by the broker
// Addrs sets the host addresses to be used by the broker.
func Addrs(addrs ...string) Option {
return func(o *Options) {
o.Addrs = addrs
@@ -95,7 +97,7 @@ func Addrs(addrs ...string) Option {
}
// Codec sets the codec used for encoding/decoding used where
// a broker does not support headers
// a broker does not support headers.
func Codec(c codec.Marshaler) Option {
return func(o *Options) {
o.Codec = c
@@ -111,14 +113,14 @@ func DisableAutoAck() SubscribeOption {
}
// ErrorHandler will catch all broker errors that cant be handled
// in normal way, for example Codec errors
// in normal way, for example Codec errors.
func ErrorHandler(h Handler) Option {
return func(o *Options) {
o.ErrorHandler = h
}
}
// Queue sets the name of the queue to share messages on
// Queue sets the name of the queue to share messages on.
func Queue(name string) SubscribeOption {
return func(o *SubscribeOptions) {
o.Queue = name
@@ -131,28 +133,28 @@ func Registry(r registry.Registry) Option {
}
}
// Secure communication with the broker
// Secure communication with the broker.
func Secure(b bool) Option {
return func(o *Options) {
o.Secure = b
}
}
// Specify TLS Config
// Specify TLS Config.
func TLSConfig(t *tls.Config) Option {
return func(o *Options) {
o.TLSConfig = t
}
}
// Logger sets the underline logger
// Logger sets the underline logger.
func Logger(l logger.Logger) Option {
return func(o *Options) {
o.Logger = l
}
}
// SubscribeContext set context
// SubscribeContext set context.
func SubscribeContext(ctx context.Context) SubscribeOption {
return func(o *SubscribeOptions) {
o.Context = ctx
+1 -1
View File
@@ -8,9 +8,9 @@ import (
type memCache struct {
opts Options
sync.RWMutex
items map[string]Item
sync.RWMutex
}
func (c *memCache) Get(ctx context.Context, key string) (interface{}, time.Time, error) {
+7 -7
View File
@@ -9,14 +9,14 @@ import (
// Options represents the options for the cache.
type Options struct {
Expiration time.Duration
Items map[string]Item
// Address represents the address or other connection information of the cache service.
Address string
// Context should contain all implementation specific options, using context.WithValue.
Context context.Context
// Logger is the be used logger
Logger logger.Logger
Items map[string]Item
// Address represents the address or other connection information of the cache service.
Address string
Expiration time.Duration
}
// Option manipulates the Options passed.
@@ -36,21 +36,21 @@ func Items(i map[string]Item) Option {
}
}
// WithAddress sets the cache service address or connection information
// WithAddress sets the cache service address or connection information.
func WithAddress(addr string) Option {
return func(o *Options) {
o.Address = addr
}
}
// WithContext sets the cache context, for any extra configuration
// WithContext sets the cache context, for any extra configuration.
func WithContext(c context.Context) Option {
return func(o *Options) {
o.Context = c
}
}
// WithLogger sets underline logger
// WithLogger sets underline logger.
func WithLogger(l logger.Logger) Option {
return func(o *Options) {
o.Logger = l
+11 -7
View File
@@ -8,36 +8,39 @@ import (
"time"
cache "github.com/patrickmn/go-cache"
"go-micro.dev/v4/metadata"
"go-micro.dev/v4/transport/headers"
)
// NewCache returns an initialised cache.
// NewCache returns an initialized cache.
func NewCache() *Cache {
return &Cache{
cache: cache.New(cache.NoExpiration, 30*time.Second),
}
}
// Cache for responses
// Cache for responses.
type Cache struct {
cache *cache.Cache
}
// Get a response from the cache
// Get a response from the cache.
func (c *Cache) Get(ctx context.Context, req *Request) (interface{}, bool) {
return c.cache.Get(key(ctx, req))
}
// Set a response in the cache
// Set a response in the cache.
func (c *Cache) Set(ctx context.Context, req *Request, rsp interface{}, expiry time.Duration) {
c.cache.Set(key(ctx, req), rsp, expiry)
}
// List the key value pairs in the cache
// List the key value pairs in the cache.
func (c *Cache) List() map[string]string {
items := c.cache.Items()
rsp := make(map[string]string, len(items))
for k, v := range items {
bytes, _ := json.Marshal(v.Object)
rsp[k] = string(bytes)
@@ -46,9 +49,9 @@ func (c *Cache) List() map[string]string {
return rsp
}
// key returns a hash for the context and request
// key returns a hash for the context and request.
func key(ctx context.Context, req *Request) string {
ns, _ := metadata.Get(ctx, "Micro-Namespace")
ns, _ := metadata.Get(ctx, headers.Namespace)
bytes, _ := json.Marshal(map[string]interface{}{
"namespace": ns,
@@ -62,5 +65,6 @@ func key(ctx context.Context, req *Request) string {
h := fnv.New64()
h.Write(bytes)
return fmt.Sprintf("%x", h.Sum(nil))
}
+21 -35
View File
@@ -3,11 +3,17 @@ package client
import (
"context"
"time"
"go-micro.dev/v4/codec"
)
var (
// NewClient returns a new client.
NewClient func(...Option) Client = newRPCClient
// DefaultClient is a default client to use out of the box.
DefaultClient Client = newRPCClient()
)
// Client is the interface used to make requests to services.
// It supports Request/Response via Transport and Publishing via the Broker.
// It also supports bidirectional streaming of requests.
@@ -22,19 +28,19 @@ type Client interface {
String() string
}
// Router manages request routing
// Router manages request routing.
type Router interface {
SendRequest(context.Context, Request) (Response, error)
}
// Message is the interface for publishing asynchronously
// Message is the interface for publishing asynchronously.
type Message interface {
Topic() string
Payload() interface{}
ContentType() string
}
// Request is the interface for a synchronous request used by Call or Stream
// Request is the interface for a synchronous request used by Call or Stream.
type Request interface {
// The service to call
Service() string
@@ -52,7 +58,7 @@ type Request interface {
Stream() bool
}
// Response is the response received from a service
// Response is the response received from a service.
type Response interface {
// Read the response
Codec() codec.Reader
@@ -62,7 +68,7 @@ type Response interface {
Read() ([]byte, error)
}
// Stream is the inteface for a bidirectional synchronous stream
// Stream is the inteface for a bidirectional synchronous stream.
type Stream interface {
Closer
// Context for the stream
@@ -81,48 +87,28 @@ type Stream interface {
Close() error
}
// Closer handle client close
// Closer handle client close.
type Closer interface {
// CloseSend closes the send direction of the stream.
CloseSend() error
}
// Option used by the Client
// Option used by the Client.
type Option func(*Options)
// CallOption used by Call or Stream
// CallOption used by Call or Stream.
type CallOption func(*CallOptions)
// PublishOption used by Publish
// PublishOption used by Publish.
type PublishOption func(*PublishOptions)
// MessageOption used by NewMessage
// MessageOption used by NewMessage.
type MessageOption func(*MessageOptions)
// RequestOption used by NewRequest
// RequestOption used by NewRequest.
type RequestOption func(*RequestOptions)
var (
// DefaultClient is a default client to use out of the box
DefaultClient Client = newRpcClient()
// DefaultBackoff is the default backoff function for retries
DefaultBackoff = exponentialBackoff
// DefaultRetry is the default check-for-retry function for retries
DefaultRetry = RetryOnError
// DefaultRetries is the default number of times a request is tried
DefaultRetries = 1
// 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
// NewClient returns a new client
NewClient func(...Option) Client = newRpcClient
)
// Makes a synchronous call to a service using the default client
// Makes a synchronous call to a service using the default client.
func Call(ctx context.Context, request Request, response interface{}, opts ...CallOption) error {
return DefaultClient.Call(ctx, request, response, opts...)
}
@@ -133,13 +119,13 @@ func Publish(ctx context.Context, msg Message, opts ...PublishOption) error {
return DefaultClient.Publish(ctx, msg, opts...)
}
// Creates a new message using the default client
// Creates a new message using the default client.
func NewMessage(topic string, payload interface{}, opts ...MessageOption) Message {
return DefaultClient.NewMessage(topic, payload, opts...)
}
// Creates a new request using the default client. Content Type will
// be set to the default within options and use the appropriate codec
// be set to the default within options and use the appropriate codec.
func NewRequest(service, endpoint string, request interface{}, reqOpts ...RequestOption) Request {
return DefaultClient.NewRequest(service, endpoint, request, reqOpts...)
}
+115 -81
View File
@@ -12,32 +12,38 @@ import (
"go-micro.dev/v4/transport"
)
type Options struct {
// Used to select codec
ContentType string
var (
// DefaultBackoff is the default backoff function for retries.
DefaultBackoff = exponentialBackoff
// DefaultRetry is the default check-for-retry function for retries.
DefaultRetry = RetryOnError
// DefaultRetries is the default number of times a request is tried.
DefaultRetries = 5
// DefaultRequestTimeout is the default request timeout.
DefaultRequestTimeout = time.Second * 30
// DefaultConnectionTimeout is the default connection timeout.
DefaultConnectionTimeout = time.Second * 5
// DefaultPoolSize sets the connection pool size.
DefaultPoolSize = 100
// DefaultPoolTTL sets the connection pool ttl.
DefaultPoolTTL = time.Minute
)
// Plugged interfaces
Broker broker.Broker
Codecs map[string]codec.NewCodec
Registry registry.Registry
Selector selector.Selector
Transport transport.Transport
// Options are the Client options.
type Options struct {
// Default Call Options
CallOptions CallOptions
// Router sets the router
Router Router
// Connection Pool
PoolSize int
PoolTTL time.Duration
Registry registry.Registry
Selector selector.Selector
Transport transport.Transport
// Response cache
Cache *Cache
// Middleware for client
Wrappers []Wrapper
// Default Call Options
CallOptions CallOptions
// Plugged interfaces
Broker broker.Broker
// Logger is the underline logger
Logger logger.Logger
@@ -45,44 +51,65 @@ type Options struct {
// Other options for implementations of the interface
// can be stored in a context
Context context.Context
Codecs map[string]codec.NewCodec
// Response cache
Cache *Cache
// Used to select codec
ContentType string
// Middleware for client
Wrappers []Wrapper
// Connection Pool
PoolSize int
PoolTTL time.Duration
}
// CallOptions are options used to make calls to a server.
type CallOptions struct {
// Other options for implementations of the interface
// can be stored in a context
Context context.Context
// Backoff func
Backoff BackoffFunc
// Check if retriable func
Retry RetryFunc
SelectOptions []selector.SelectOption
// Address of remote hosts
Address []string
// Backoff func
Backoff BackoffFunc
// Check if retriable func
Retry RetryFunc
// Transport Dial Timeout
DialTimeout time.Duration
// Number of Call attempts
Retries int
// Request/Response timeout
RequestTimeout time.Duration
// Stream timeout for the stream
StreamTimeout time.Duration
// Use the services own auth token
ServiceToken bool
// Duration to cache the response for
CacheExpiry time.Duration
// Middleware for low level call func
CallWrappers []CallWrapper
// Other options for implementations of the interface
// can be stored in a context
Context context.Context
// ConnectionTimeout of one request to the server.
// Set this lower than the RequestTimeout to enbale retries on connection timeout.
ConnectionTimeout time.Duration
// Request/Response timeout of entire srv.Call, for single request timeout set ConnectionTimeout.
RequestTimeout time.Duration
// Stream timeout for the stream
StreamTimeout time.Duration
// Duration to cache the response for
CacheExpiry time.Duration
// Transport Dial Timeout. Used for initial dial to establish a connection.
DialTimeout time.Duration
// Number of Call attempts
Retries int
// Use the services own auth token
ServiceToken bool
// ConnClose sets the Connection: close header.
ConnClose bool
}
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 context.Context
// Exchange is the routing exchange for the message
Exchange string
}
type MessageOptions struct {
@@ -90,14 +117,15 @@ type MessageOptions struct {
}
type RequestOptions struct {
ContentType string
Stream bool
// Other options for implementations of the interface
// can be stored in a context
Context context.Context
Context context.Context
ContentType string
Stream bool
}
// NewOptions creates new Client options.
func NewOptions(options ...Option) Options {
opts := Options{
Cache: NewCache(),
@@ -105,11 +133,12 @@ func NewOptions(options ...Option) Options {
ContentType: DefaultContentType,
Codecs: make(map[string]codec.NewCodec),
CallOptions: CallOptions{
Backoff: DefaultBackoff,
Retry: DefaultRetry,
Retries: DefaultRetries,
RequestTimeout: DefaultRequestTimeout,
DialTimeout: transport.DefaultDialTimeout,
Backoff: DefaultBackoff,
Retry: DefaultRetry,
Retries: DefaultRetries,
RequestTimeout: DefaultRequestTimeout,
ConnectionTimeout: DefaultConnectionTimeout,
DialTimeout: transport.DefaultDialTimeout,
},
PoolSize: DefaultPoolSize,
PoolTTL: DefaultPoolTTL,
@@ -127,42 +156,42 @@ func NewOptions(options ...Option) Options {
return opts
}
// Broker to be used for pub/sub
// Broker to be used for pub/sub.
func Broker(b broker.Broker) Option {
return func(o *Options) {
o.Broker = b
}
}
// Codec to be used to encode/decode requests for a given content type
// Codec to be used to encode/decode requests for a given content type.
func Codec(contentType string, c codec.NewCodec) Option {
return func(o *Options) {
o.Codecs[contentType] = c
}
}
// Default content type of the client
// ContentType sets the default content type of the client.
func ContentType(ct string) Option {
return func(o *Options) {
o.ContentType = ct
}
}
// PoolSize sets the connection pool size
// PoolSize sets the connection pool size.
func PoolSize(d int) Option {
return func(o *Options) {
o.PoolSize = d
}
}
// PoolTTL sets the connection pool ttl
// PoolTTL sets the connection pool ttl.
func PoolTTL(d time.Duration) Option {
return func(o *Options) {
o.PoolTTL = d
}
}
// Registry to find nodes for a given service
// Registry to find nodes for a given service.
func Registry(r registry.Registry) Option {
return func(o *Options) {
o.Registry = r
@@ -171,28 +200,28 @@ func Registry(r registry.Registry) Option {
}
}
// Transport to use for communication e.g http, rabbitmq, etc
// Transport to use for communication e.g http, rabbitmq, etc.
func Transport(t transport.Transport) Option {
return func(o *Options) {
o.Transport = t
}
}
// Select is used to select a node to route a request to
// Select is used to select a node to route a request to.
func Selector(s selector.Selector) Option {
return func(o *Options) {
o.Selector = s
}
}
// Adds a Wrapper to a list of options passed into the client
// Adds a Wrapper to a 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
// 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...)
@@ -200,15 +229,14 @@ func WrapCall(cw ...CallWrapper) Option {
}
// Backoff is used to set the backoff function used
// when retrying Calls
// when retrying Calls.
func Backoff(fn BackoffFunc) Option {
return func(o *Options) {
o.CallOptions.Backoff = fn
}
}
// Number of retries when making the request.
// Should this be a Call Option?
// Retries set the number of retries when making the request.
func Retries(i int) Option {
return func(o *Options) {
o.CallOptions.Retries = i
@@ -222,22 +250,21 @@ func Retry(fn RetryFunc) Option {
}
}
// The request timeout.
// Should this be a Call Option?
// RequestTimeout set the request timeout.
func RequestTimeout(d time.Duration) Option {
return func(o *Options) {
o.CallOptions.RequestTimeout = d
}
}
// StreamTimeout sets the stream timeout
// StreamTimeout sets the stream timeout.
func StreamTimeout(d time.Duration) Option {
return func(o *Options) {
o.CallOptions.StreamTimeout = d
}
}
// Transport dial timeout
// DialTimeout sets the transport dial timeout.
func DialTimeout(d time.Duration) Option {
return func(o *Options) {
o.CallOptions.DialTimeout = d
@@ -246,21 +273,21 @@ func DialTimeout(d time.Duration) Option {
// Call Options
// WithExchange sets the exchange to route a message through
// WithExchange sets the exchange to route a message through.
func WithExchange(e string) PublishOption {
return func(o *PublishOptions) {
o.Exchange = e
}
}
// PublishContext sets the context in publish options
// PublishContext sets the context in publish options.
func PublishContext(ctx context.Context) PublishOption {
return func(o *PublishOptions) {
o.Context = ctx
}
}
// WithAddress sets the remote addresses to use rather than using service discovery
// WithAddress sets the remote addresses to use rather than using service discovery.
func WithAddress(a ...string) CallOption {
return func(o *CallOptions) {
o.Address = a
@@ -273,7 +300,7 @@ func WithSelectOption(so ...selector.SelectOption) CallOption {
}
}
// WithCallWrapper is a CallOption which adds to the existing CallFunc wrappers
// WithCallWrapper is a CallOption which adds to the existing CallFunc wrappers.
func WithCallWrapper(cw ...CallWrapper) CallOption {
return func(o *CallOptions) {
o.CallWrappers = append(o.CallWrappers, cw...)
@@ -281,7 +308,7 @@ func WithCallWrapper(cw ...CallWrapper) CallOption {
}
// WithBackoff is a CallOption which overrides that which
// set in Options.CallOptions
// set in Options.CallOptions.
func WithBackoff(fn BackoffFunc) CallOption {
return func(o *CallOptions) {
o.Backoff = fn
@@ -289,15 +316,15 @@ func WithBackoff(fn BackoffFunc) CallOption {
}
// WithRetry is a CallOption which overrides that which
// set in Options.CallOptions
// set in Options.CallOptions.
func WithRetry(fn RetryFunc) CallOption {
return func(o *CallOptions) {
o.Retry = fn
}
}
// WithRetries is a CallOption which overrides that which
// set in Options.CallOptions
// WithRetries sets the number of tries for a call.
// This CallOption overrides Options.CallOptions.
func WithRetries(i int) CallOption {
return func(o *CallOptions) {
o.Retries = i
@@ -305,14 +332,21 @@ func WithRetries(i int) CallOption {
}
// WithRequestTimeout is a CallOption which overrides that which
// set in Options.CallOptions
// set in Options.CallOptions.
func WithRequestTimeout(d time.Duration) CallOption {
return func(o *CallOptions) {
o.RequestTimeout = d
}
}
// WithStreamTimeout sets the stream timeout
// WithConnClose sets the Connection header to close.
func WithConnClose() CallOption {
return func(o *CallOptions) {
o.ConnClose = true
}
}
// WithStreamTimeout sets the stream timeout.
func WithStreamTimeout(d time.Duration) CallOption {
return func(o *CallOptions) {
o.StreamTimeout = d
@@ -320,7 +354,7 @@ func WithStreamTimeout(d time.Duration) CallOption {
}
// WithDialTimeout is a CallOption which overrides that which
// set in Options.CallOptions
// set in Options.CallOptions.
func WithDialTimeout(d time.Duration) CallOption {
return func(o *CallOptions) {
o.DialTimeout = d
@@ -328,7 +362,7 @@ func WithDialTimeout(d time.Duration) CallOption {
}
// WithServiceToken is a CallOption which overrides the
// authorization header with the services own auth token
// authorization header with the services own auth token.
func WithServiceToken() CallOption {
return func(o *CallOptions) {
o.ServiceToken = true
@@ -336,7 +370,7 @@ func WithServiceToken() CallOption {
}
// WithCache is a CallOption which sets the duration the response
// shoull be cached for
// shoull be cached for.
func WithCache(c time.Duration) CallOption {
return func(o *CallOptions) {
o.CacheExpiry = c
@@ -363,14 +397,14 @@ func StreamingRequest() RequestOption {
}
}
// WithRouter sets the client router
// WithRouter sets the client router.
func WithRouter(r Router) Option {
return func(o *Options) {
o.Router = r
}
}
// WithLogger sets the underline logger
// WithLogger sets the underline logger.
func WithLogger(l logger.Logger) Option {
return func(o *Options) {
o.Logger = l
+6 -5
View File
@@ -6,15 +6,15 @@ import (
"go-micro.dev/v4/errors"
)
// note that returning either false or a non-nil error will result in the call not being retried
// note that returning either false or a non-nil error will result in the call not being retried.
type RetryFunc func(ctx context.Context, req Request, retryCount int, err error) (bool, error)
// RetryAlways always retry on error
// RetryAlways always retry on error.
func RetryAlways(ctx context.Context, req Request, retryCount int, err error) (bool, error) {
return true, nil
}
// RetryOnError retries a request on a 500 or timeout error
// RetryOnError retries a request on a 500 or timeout error.
func RetryOnError(ctx context.Context, req Request, retryCount int, err error) (bool, error) {
if err == nil {
return false, nil
@@ -26,8 +26,9 @@ func RetryOnError(ctx context.Context, req Request, retryCount int, err error) (
}
switch e.Code {
// retry on timeout or internal server error
case 408, 500:
// Retry on timeout, not on 500 internal server error, as that is a business
// logic error that should be handled by the user.
case 408:
return true, nil
default:
return false, nil
+152 -64
View File
@@ -3,31 +3,43 @@ package client
import (
"context"
"fmt"
"sync"
"sync/atomic"
"time"
"github.com/google/uuid"
"github.com/pkg/errors"
"go-micro.dev/v4/broker"
"go-micro.dev/v4/codec"
raw "go-micro.dev/v4/codec/bytes"
"go-micro.dev/v4/errors"
merrors "go-micro.dev/v4/errors"
log "go-micro.dev/v4/logger"
"go-micro.dev/v4/metadata"
"go-micro.dev/v4/registry"
"go-micro.dev/v4/selector"
"go-micro.dev/v4/transport"
"go-micro.dev/v4/transport/headers"
"go-micro.dev/v4/util/buf"
"go-micro.dev/v4/util/net"
"go-micro.dev/v4/util/pool"
)
const (
packageID = "go.micro.client"
)
type rpcClient struct {
seq uint64
once atomic.Value
opts Options
once atomic.Value
pool pool.Pool
seq uint64
mu sync.RWMutex
}
func newRpcClient(opt ...Option) Client {
func newRPCClient(opt ...Option) Client {
opts := NewOptions(opt...)
p := pool.NewPool(
@@ -57,14 +69,17 @@ func (r *rpcClient) newCodec(contentType string) (codec.NewCodec, error) {
if c, ok := r.opts.Codecs[contentType]; ok {
return c, nil
}
if cf, ok := DefaultCodecs[contentType]; ok {
return cf, nil
}
return nil, fmt.Errorf("unsupported Content-Type: %s", contentType)
}
func (r *rpcClient) call(ctx context.Context, node *registry.Node, req Request, resp interface{}, opts CallOptions) error {
address := node.Address
logger := r.Options().Logger
msg := &transport.Message{
Header: make(map[string]string),
@@ -73,31 +88,43 @@ func (r *rpcClient) call(ctx context.Context, node *registry.Node, req Request,
md, ok := metadata.FromContext(ctx)
if ok {
for k, v := range md {
// don't copy Micro-Topic header, that used for pub/sub
// this fix case then client uses the same context that received in subscriber
if k == "Micro-Topic" {
// Don't copy Micro-Topic header, that is used for pub/sub
// this is fixes the case when the client uses the same context that
// is received in the subscriber.
if k == headers.Message {
continue
}
msg.Header[k] = v
}
}
// Set connection timeout for single requests to the server. Should be > 0
// as otherwise requests can't be made.
cTimeout := opts.ConnectionTimeout
if cTimeout == 0 {
logger.Log(log.DebugLevel, "connection timeout was set to 0, overridng to default connection timeout")
cTimeout = DefaultConnectionTimeout
}
// set timeout in nanoseconds
msg.Header["Timeout"] = fmt.Sprintf("%d", opts.RequestTimeout)
msg.Header["Timeout"] = fmt.Sprintf("%d", cTimeout)
// set the content type for the request
msg.Header["Content-Type"] = req.ContentType()
// set the accept header
msg.Header["Accept"] = req.ContentType()
// setup old protocol
cf := setupProtocol(msg, node)
reqCodec := setupProtocol(msg, node)
// no codec specified
if cf == nil {
if reqCodec == nil {
var err error
cf, err = r.newCodec(req.ContentType())
reqCodec, err = r.newCodec(req.ContentType())
if err != nil {
return errors.InternalServerError("go.micro.client", err.Error())
return merrors.InternalServerError("go.micro.client", err.Error())
}
}
@@ -109,19 +136,29 @@ func (r *rpcClient) call(ctx context.Context, node *registry.Node, req Request,
dOpts = append(dOpts, transport.WithTimeout(opts.DialTimeout))
}
if opts.ConnClose {
dOpts = append(dOpts, transport.WithConnClose())
}
c, err := r.pool.Get(address, dOpts...)
if err != nil {
return errors.InternalServerError("go.micro.client", "connection error: %v", err)
return merrors.InternalServerError("go.micro.client", "connection error: %v", err)
}
seq := atomic.AddUint64(&r.seq, 1) - 1
codec := newRpcCodec(msg, c, cf, "")
codec := newRPCCodec(msg, c, reqCodec, "")
rsp := &rpcResponse{
socket: c,
codec: codec,
}
releaseFunc := func(err error) {
if err = r.pool.Release(c, err); err != nil {
logger.Log(log.ErrorLevel, "failed to release pool", err)
}
}
stream := &rpcStream{
id: fmt.Sprintf("%v", seq),
context: ctx,
@@ -129,11 +166,17 @@ func (r *rpcClient) call(ctx context.Context, node *registry.Node, req Request,
response: rsp,
codec: codec,
closed: make(chan bool),
release: func(err error) { r.pool.Release(c, err) },
close: opts.ConnClose,
release: releaseFunc,
sendEOS: false,
}
// close the stream on exiting this function
defer stream.Close()
defer func() {
if err := stream.Close(); err != nil {
logger.Log(log.ErrorLevel, "failed to close stream", err)
}
}()
// wait for error response
ch := make(chan error, 1)
@@ -141,7 +184,7 @@ func (r *rpcClient) call(ctx context.Context, node *registry.Node, req Request,
go func() {
defer func() {
if r := recover(); r != nil {
ch <- errors.InternalServerError("go.micro.client", "panic recovered: %v", r)
ch <- merrors.InternalServerError("go.micro.client", "panic recovered: %v", r)
}
}()
@@ -166,8 +209,8 @@ func (r *rpcClient) call(ctx context.Context, node *registry.Node, req Request,
select {
case err := <-ch:
return err
case <-ctx.Done():
grr = errors.Timeout("go.micro.client", fmt.Sprintf("%v", ctx.Err()))
case <-time.After(cTimeout):
grr = merrors.Timeout("go.micro.client", fmt.Sprintf("%v", ctx.Err()))
}
// set the stream error
@@ -184,6 +227,7 @@ func (r *rpcClient) call(ctx context.Context, node *registry.Node, req Request,
func (r *rpcClient) stream(ctx context.Context, node *registry.Node, req Request, opts CallOptions) (Stream, error) {
address := node.Address
logger := r.Options().Logger
msg := &transport.Message{
Header: make(map[string]string),
@@ -206,14 +250,15 @@ func (r *rpcClient) stream(ctx context.Context, node *registry.Node, req Request
msg.Header["Accept"] = req.ContentType()
// set old codecs
cf := setupProtocol(msg, node)
nCodec := setupProtocol(msg, node)
// no codec specified
if cf == nil {
if nCodec == nil {
var err error
cf, err = r.newCodec(req.ContentType())
nCodec, err = r.newCodec(req.ContentType())
if err != nil {
return nil, errors.InternalServerError("go.micro.client", err.Error())
return nil, merrors.InternalServerError("go.micro.client", err.Error())
}
}
@@ -227,7 +272,7 @@ func (r *rpcClient) stream(ctx context.Context, node *registry.Node, req Request
c, err := r.opts.Transport.Dial(address, dOpts...)
if err != nil {
return nil, errors.InternalServerError("go.micro.client", "connection error: %v", err)
return nil, merrors.InternalServerError("go.micro.client", "connection error: %v", err)
}
// increment the sequence number
@@ -235,7 +280,7 @@ func (r *rpcClient) stream(ctx context.Context, node *registry.Node, req Request
id := fmt.Sprintf("%v", seq)
// create codec with stream id
codec := newRpcCodec(msg, c, cf, id)
codec := newRPCCodec(msg, c, nCodec, id)
rsp := &rpcResponse{
socket: c,
@@ -247,6 +292,12 @@ func (r *rpcClient) stream(ctx context.Context, node *registry.Node, req Request
r.codec = codec
}
releaseFunc := func(_ error) {
if err = c.Close(); err != nil {
logger.Log(log.ErrorLevel, err)
}
}
stream := &rpcStream{
id: id,
context: ctx,
@@ -257,8 +308,7 @@ func (r *rpcClient) stream(ctx context.Context, node *registry.Node, req Request
closed: make(chan bool),
// signal the end of stream,
sendEOS: true,
// release func
release: func(err error) { c.Close() },
release: releaseFunc,
}
// wait for error response
@@ -275,7 +325,7 @@ func (r *rpcClient) stream(ctx context.Context, node *registry.Node, req Request
case err := <-ch:
grr = err
case <-ctx.Done():
grr = errors.Timeout("go.micro.client", fmt.Sprintf("%v", ctx.Err()))
grr = merrors.Timeout("go.micro.client", fmt.Sprintf("%v", ctx.Err()))
}
if grr != nil {
@@ -285,7 +335,10 @@ func (r *rpcClient) stream(ctx context.Context, node *registry.Node, req Request
stream.Unlock()
// close the stream
stream.Close()
if err := stream.Close(); err != nil {
logger.Logf(log.ErrorLevel, "failed to close stream: %v", err)
}
return nil, grr
}
@@ -293,6 +346,9 @@ func (r *rpcClient) stream(ctx context.Context, node *registry.Node, req Request
}
func (r *rpcClient) Init(opts ...Option) error {
r.mu.Lock()
defer r.mu.Unlock()
size := r.opts.PoolSize
ttl := r.opts.PoolTTL
tr := r.opts.Transport
@@ -304,7 +360,10 @@ func (r *rpcClient) Init(opts ...Option) error {
// update pool configuration if the options changed
if size != r.opts.PoolSize || ttl != r.opts.PoolTTL || tr != r.opts.Transport {
// close existing pool
r.pool.Close()
if err := r.pool.Close(); err != nil {
return errors.Wrap(err, "failed to close pool")
}
// create new pool
r.pool = pool.NewPool(
pool.Size(r.opts.PoolSize),
@@ -316,11 +375,15 @@ func (r *rpcClient) Init(opts ...Option) error {
return nil
}
// Options retrives the options.
func (r *rpcClient) Options() Options {
r.mu.RLock()
defer r.mu.RUnlock()
return r.opts
}
// next returns an iterator for the next nodes to call
// next returns an iterator for the next nodes to call.
func (r *rpcClient) next(request Request, opts CallOptions) (selector.Next, error) {
// try get the proxy
service, address, _ := net.Proxy(request.Service(), opts.Address)
@@ -348,16 +411,22 @@ func (r *rpcClient) next(request Request, opts CallOptions) (selector.Next, erro
// get next nodes from the selector
next, err := r.opts.Selector.Select(service, opts.SelectOptions...)
if err != nil {
if err == selector.ErrNotFound {
return nil, errors.InternalServerError("go.micro.client", "service %s: %s", service, err.Error())
if errors.Is(err, selector.ErrNotFound) {
return nil, merrors.InternalServerError("go.micro.client", "service %s: %s", service, err.Error())
}
return nil, errors.InternalServerError("go.micro.client", "error selecting %s node: %s", service, err.Error())
return nil, merrors.InternalServerError("go.micro.client", "error selecting %s node: %s", service, err.Error())
}
return next, nil
}
func (r *rpcClient) Call(ctx context.Context, request Request, response interface{}, opts ...CallOption) error {
// TODO: further validate these mutex locks. full lock would prevent
// parallel calls. Maybe we can set individual locks for secctions.
r.mu.RLock()
defer r.mu.RUnlock()
// make a copy of call opts
callOpts := r.opts.CallOptions
for _, opt := range opts {
@@ -375,6 +444,7 @@ func (r *rpcClient) Call(ctx context.Context, request Request, response interfac
// no deadline so we create a new one
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, callOpts.RequestTimeout)
defer cancel()
} else {
// got a deadline so no need to setup context
@@ -386,7 +456,7 @@ func (r *rpcClient) Call(ctx context.Context, request Request, response interfac
// should we noop right here?
select {
case <-ctx.Done():
return errors.Timeout("go.micro.client", fmt.Sprintf("%v", ctx.Err()))
return merrors.Timeout("go.micro.client", fmt.Sprintf("%v", ctx.Err()))
default:
}
@@ -403,7 +473,7 @@ func (r *rpcClient) Call(ctx context.Context, request Request, response interfac
// call backoff first. Someone may want an initial start delay
t, err := callOpts.Backoff(ctx, request, i)
if err != nil {
return errors.InternalServerError("go.micro.client", "backoff error: %v", err.Error())
return merrors.InternalServerError("go.micro.client", "backoff error: %v", err.Error())
}
// only sleep if greater than 0
@@ -414,16 +484,19 @@ func (r *rpcClient) Call(ctx context.Context, request Request, response interfac
// select next node
node, err := next()
service := request.Service()
if err != nil {
if err == selector.ErrNotFound {
return errors.InternalServerError("go.micro.client", "service %s: %s", service, err.Error())
if errors.Is(err, selector.ErrNotFound) {
return merrors.InternalServerError("go.micro.client", "service %s: %s", service, err.Error())
}
return errors.InternalServerError("go.micro.client", "error getting next %s node: %s", service, err.Error())
return merrors.InternalServerError("go.micro.client", "error getting next %s node: %s", service, err.Error())
}
// make the call
err = rcall(ctx, node, request, response, callOpts)
r.opts.Selector.Mark(service, node, err)
return err
}
@@ -431,11 +504,13 @@ func (r *rpcClient) Call(ctx context.Context, request Request, response interfac
retries := callOpts.Retries
// disable retries when using a proxy
if _, _, ok := net.Proxy(request.Service(), callOpts.Address); ok {
retries = 0
}
// Note: I don't see why we should disable retries for proxies, so commenting out.
// if _, _, ok := net.Proxy(request.Service(), callOpts.Address); ok {
// retries = 0
// }
ch := make(chan error, retries+1)
var gerr error
for i := 0; i <= retries; i++ {
@@ -445,7 +520,7 @@ func (r *rpcClient) Call(ctx context.Context, request Request, response interfac
select {
case <-ctx.Done():
return errors.Timeout("go.micro.client", fmt.Sprintf("call timeout: %v", ctx.Err()))
return merrors.Timeout("go.micro.client", fmt.Sprintf("call timeout: %v", ctx.Err()))
case err := <-ch:
// if the call succeeded lets bail early
if err == nil {
@@ -461,6 +536,8 @@ func (r *rpcClient) Call(ctx context.Context, request Request, response interfac
return err
}
r.opts.Logger.Logf(log.DebugLevel, "Retrying request. Previous attempt failed with: %v", err)
gerr = err
}
}
@@ -469,6 +546,9 @@ func (r *rpcClient) Call(ctx context.Context, request Request, response interfac
}
func (r *rpcClient) Stream(ctx context.Context, request Request, opts ...CallOption) (Stream, error) {
r.mu.RLock()
defer r.mu.RUnlock()
// make a copy of call opts
callOpts := r.opts.CallOptions
for _, opt := range opts {
@@ -480,10 +560,9 @@ func (r *rpcClient) Stream(ctx context.Context, request Request, opts ...CallOpt
return nil, err
}
// should we noop right here?
select {
case <-ctx.Done():
return nil, errors.Timeout("go.micro.client", fmt.Sprintf("%v", ctx.Err()))
return nil, merrors.Timeout("go.micro.client", fmt.Sprintf("%v", ctx.Err()))
default:
}
@@ -491,7 +570,7 @@ func (r *rpcClient) Stream(ctx context.Context, request Request, opts ...CallOpt
// call backoff first. Someone may want an initial start delay
t, err := callOpts.Backoff(ctx, request, i)
if err != nil {
return nil, errors.InternalServerError("go.micro.client", "backoff error: %v", err.Error())
return nil, merrors.InternalServerError("go.micro.client", "backoff error: %v", err.Error())
}
// only sleep if greater than 0
@@ -501,15 +580,18 @@ func (r *rpcClient) Stream(ctx context.Context, request Request, opts ...CallOpt
node, err := next()
service := request.Service()
if err != nil {
if err == selector.ErrNotFound {
return nil, errors.InternalServerError("go.micro.client", "service %s: %s", service, err.Error())
if errors.Is(err, selector.ErrNotFound) {
return nil, merrors.InternalServerError("go.micro.client", "service %s: %s", service, err.Error())
}
return nil, errors.InternalServerError("go.micro.client", "error getting next %s node: %s", service, err.Error())
return nil, merrors.InternalServerError("go.micro.client", "error getting next %s node: %s", service, err.Error())
}
stream, err := r.stream(ctx, node, request, callOpts)
r.opts.Selector.Mark(service, node, err)
return stream, err
}
@@ -527,6 +609,7 @@ func (r *rpcClient) Stream(ctx context.Context, request Request, opts ...CallOpt
}
ch := make(chan response, retries+1)
var grr error
for i := 0; i <= retries; i++ {
@@ -537,7 +620,7 @@ func (r *rpcClient) Stream(ctx context.Context, request Request, opts ...CallOpt
select {
case <-ctx.Done():
return nil, errors.Timeout("go.micro.client", fmt.Sprintf("call timeout: %v", ctx.Err()))
return nil, merrors.Timeout("go.micro.client", fmt.Sprintf("call timeout: %v", ctx.Err()))
case rsp := <-ch:
// if the call succeeded lets bail early
if rsp.err == nil {
@@ -568,15 +651,15 @@ func (r *rpcClient) Publish(ctx context.Context, msg Message, opts ...PublishOpt
o(&options)
}
md, ok := metadata.FromContext(ctx)
metadata, ok := metadata.FromContext(ctx)
if !ok {
md = make(map[string]string)
metadata = make(map[string]string)
}
id := uuid.New().String()
md["Content-Type"] = msg.ContentType()
md["Micro-Topic"] = msg.Topic()
md["Micro-Id"] = id
metadata["Content-Type"] = msg.ContentType()
metadata[headers.Message] = msg.Topic()
metadata[headers.ID] = id
// set the topic
topic := msg.Topic()
@@ -589,7 +672,7 @@ func (r *rpcClient) Publish(ctx context.Context, msg Message, opts ...PublishOpt
// encode message body
cf, err := r.newCodec(msg.ContentType())
if err != nil {
return errors.InternalServerError("go.micro.client", err.Error())
return merrors.InternalServerError(packageID, err.Error())
}
var body []byte
@@ -598,33 +681,38 @@ func (r *rpcClient) Publish(ctx context.Context, msg Message, opts ...PublishOpt
if d, ok := msg.Payload().(*raw.Frame); ok {
body = d.Data
} else {
// new buffer
b := buf.New(nil)
if err := cf(b).Write(&codec.Message{
if err = cf(b).Write(&codec.Message{
Target: topic,
Type: codec.Event,
Header: map[string]string{
"Micro-Id": id,
"Micro-Topic": msg.Topic(),
headers.ID: id,
headers.Message: msg.Topic(),
},
}, msg.Payload()); err != nil {
return errors.InternalServerError("go.micro.client", err.Error())
return merrors.InternalServerError(packageID, err.Error())
}
// set the body
body = b.Bytes()
}
if !r.once.Load().(bool) {
l, ok := r.once.Load().(bool)
if !ok {
return fmt.Errorf("failed to cast to bool")
}
if !l {
if err = r.opts.Broker.Connect(); err != nil {
return errors.InternalServerError("go.micro.client", err.Error())
return merrors.InternalServerError(packageID, err.Error())
}
r.once.Store(true)
}
return r.opts.Broker.Publish(topic, &broker.Message{
Header: md,
Header: metadata,
Body: body,
}, broker.PublishContext(options.Context))
}
+47 -34
View File
@@ -14,6 +14,7 @@ import (
"go-micro.dev/v4/errors"
"go-micro.dev/v4/registry"
"go-micro.dev/v4/transport"
"go-micro.dev/v4/transport/headers"
)
const (
@@ -28,7 +29,7 @@ func (e serverError) Error() string {
return string(e)
}
// errShutdown holds the specific error for closing/closed connections
// errShutdown holds the specific error for closing/closed connections.
var (
errShutdown = errs.New("connection is shut down")
)
@@ -50,8 +51,10 @@ type readWriteCloser struct {
}
var (
// DefaultContentType header.
DefaultContentType = "application/json"
// DefaultCodecs map.
DefaultCodecs = map[string]codec.NewCodec{
"application/grpc": grpc.NewCodec,
"application/grpc+json": grpc.NewCodec,
@@ -63,7 +66,7 @@ var (
"application/octet-stream": raw.NewCodec,
}
// TODO: remove legacy codec list
// TODO: remove legacy codec list.
defaultCodecs = map[string]codec.NewCodec{
"application/json": jsonrpc.NewCodec,
"application/json-rpc": jsonrpc.NewCodec,
@@ -84,6 +87,7 @@ func (rwc *readWriteCloser) Write(p []byte) (n int, err error) {
func (rwc *readWriteCloser) Close() error {
rwc.rbuf.Reset()
rwc.wbuf.Reset()
return nil
}
@@ -92,20 +96,21 @@ func getHeaders(m *codec.Message) {
if len(v) > 0 {
return v
}
return m.Header[hdr]
}
// check error in header
m.Error = set(m.Error, "Micro-Error")
m.Error = set(m.Error, headers.Error)
// check endpoint in header
m.Endpoint = set(m.Endpoint, "Micro-Endpoint")
m.Endpoint = set(m.Endpoint, headers.Endpoint)
// check method in header
m.Method = set(m.Method, "Micro-Method")
m.Method = set(m.Method, headers.Method)
// set the request id
m.Id = set(m.Id, "Micro-Id")
m.Id = set(m.Id, headers.ID)
}
func setHeaders(m *codec.Message, stream string) {
@@ -113,21 +118,22 @@ func setHeaders(m *codec.Message, stream string) {
if len(v) == 0 {
return
}
m.Header[hdr] = v
}
set("Micro-Id", m.Id)
set("Micro-Service", m.Target)
set("Micro-Method", m.Method)
set("Micro-Endpoint", m.Endpoint)
set("Micro-Error", m.Error)
set(headers.ID, m.Id)
set(headers.Request, m.Target)
set(headers.Method, m.Method)
set(headers.Endpoint, m.Endpoint)
set(headers.Error, m.Error)
if len(stream) > 0 {
set("Micro-Stream", stream)
set(headers.Stream, stream)
}
}
// setupProtocol sets up the old protocol
// setupProtocol sets up the old protocol.
func setupProtocol(msg *transport.Message, node *registry.Node) codec.NewCodec {
protocol := node.Metadata["protocol"]
@@ -137,7 +143,7 @@ func setupProtocol(msg *transport.Message, node *registry.Node) codec.NewCodec {
}
// processing topic publishing
if len(msg.Header["Micro-Topic"]) > 0 {
if len(msg.Header[headers.Message]) > 0 {
return nil
}
@@ -149,60 +155,59 @@ func setupProtocol(msg *transport.Message, node *registry.Node) codec.NewCodec {
msg.Header["Content-Type"] = "application/proto-rpc"
}
// now return codec
return defaultCodecs[msg.Header["Content-Type"]]
}
func newRpcCodec(req *transport.Message, client transport.Client, c codec.NewCodec, stream string) codec.Codec {
func newRPCCodec(req *transport.Message, client transport.Client, c codec.NewCodec, stream string) codec.Codec {
rwc := &readWriteCloser{
wbuf: bytes.NewBuffer(nil),
rbuf: bytes.NewBuffer(nil),
}
r := &rpcCodec{
return &rpcCodec{
buf: rwc,
client: client,
codec: c(rwc),
req: req,
stream: stream,
}
return r
}
func (c *rpcCodec) Write(m *codec.Message, body interface{}) error {
func (c *rpcCodec) Write(message *codec.Message, body interface{}) error {
c.buf.wbuf.Reset()
// create header
if m.Header == nil {
m.Header = map[string]string{}
if message.Header == nil {
message.Header = map[string]string{}
}
// copy original header
for k, v := range c.req.Header {
m.Header[k] = v
message.Header[k] = v
}
// set the mucp headers
setHeaders(m, c.stream)
setHeaders(message, c.stream)
// if body is bytes Frame don't encode
if body != nil {
if b, ok := body.(*raw.Frame); ok {
// set body
m.Body = b.Data
message.Body = b.Data
} else {
// write to codec
if err := c.codec.Write(m, body); err != nil {
if err := c.codec.Write(message, body); err != nil {
return errors.InternalServerError("go.micro.client.codec", err.Error())
}
// set body
m.Body = c.buf.wbuf.Bytes()
message.Body = c.buf.wbuf.Bytes()
}
}
// create new transport message
msg := transport.Message{
Header: m.Header,
Body: m.Body,
Header: message.Header,
Body: message.Body,
}
// send the request
@@ -213,7 +218,7 @@ func (c *rpcCodec) Write(m *codec.Message, body interface{}) error {
return nil
}
func (c *rpcCodec) ReadHeader(m *codec.Message, r codec.MessageType) error {
func (c *rpcCodec) ReadHeader(msg *codec.Message, r codec.MessageType) error {
var tm transport.Message
// read message from transport
@@ -225,13 +230,13 @@ func (c *rpcCodec) ReadHeader(m *codec.Message, r codec.MessageType) error {
c.buf.rbuf.Write(tm.Body)
// set headers from transport
m.Header = tm.Header
msg.Header = tm.Header
// read header
err := c.codec.ReadHeader(m, r)
err := c.codec.ReadHeader(msg, r)
// get headers
getHeaders(m)
getHeaders(msg)
// return header error
if err != nil {
@@ -252,15 +257,23 @@ func (c *rpcCodec) ReadBody(b interface{}) error {
if err := c.codec.ReadBody(b); err != nil {
return errors.InternalServerError("go.micro.client.codec", err.Error())
}
return nil
}
func (c *rpcCodec) Close() error {
c.buf.Close()
c.codec.Close()
if err := c.buf.Close(); err != nil {
return err
}
if err := c.codec.Close(); err != nil {
return err
}
if err := c.client.Close(); err != nil {
return errors.InternalServerError("go.micro.client.transport", err.Error())
}
return nil
}
+1 -1
View File
@@ -1,9 +1,9 @@
package client
type message struct {
payload interface{}
topic string
contentType string
payload interface{}
}
func newMessage(topic string, payload interface{}, contentType string, opts ...MessageOption) Message {
+3 -3
View File
@@ -5,13 +5,13 @@ import (
)
type rpcRequest struct {
opts RequestOptions
codec codec.Codec
body interface{}
service string
method string
endpoint string
contentType string
codec codec.Codec
body interface{}
opts RequestOptions
}
func newRequest(service, endpoint string, request interface{}, contentType string, reqOpts ...RequestOption) Request {
+2 -2
View File
@@ -6,10 +6,10 @@ import (
)
type rpcResponse struct {
header map[string]string
body []byte
socket transport.Socket
codec codec.Codec
header map[string]string
body []byte
}
func (r *rpcResponse) Codec() codec.Reader {
+24 -10
View File
@@ -9,22 +9,25 @@ import (
"go-micro.dev/v4/codec"
)
// Implements the streamer interface
// Implements the streamer interface.
type rpcStream struct {
sync.RWMutex
id string
closed chan bool
err error
request Request
response Response
codec codec.Codec
context context.Context
// signal whether we should send EOS
sendEOS bool
closed chan bool
// release releases the connection back to the pool
release func(err error)
id string
sync.RWMutex
// Indicates whether connection should be closed directly.
close bool
// signal whether we should send EOS
sendEOS bool
}
func (r *rpcStream) isClosed() bool {
@@ -79,6 +82,7 @@ func (r *rpcStream) Recv(msg interface{}) error {
if r.isClosed() {
r.err = errShutdown
r.Unlock()
return errShutdown
}
@@ -87,15 +91,19 @@ func (r *rpcStream) Recv(msg interface{}) error {
r.Unlock()
err := r.codec.ReadHeader(&resp, codec.Response)
r.Lock()
if err != nil {
if err == io.EOF && !r.isClosed() {
if errors.Is(err, io.EOF) && !r.isClosed() {
r.err = io.ErrUnexpectedEOF
r.Unlock()
return io.ErrUnexpectedEOF
}
r.err = err
r.Unlock()
return err
}
@@ -124,13 +132,15 @@ func (r *rpcStream) Recv(msg interface{}) error {
}
}
r.Unlock()
defer r.Unlock()
return r.err
}
func (r *rpcStream) Error() error {
r.RLock()
defer r.RUnlock()
return r.err
}
@@ -152,6 +162,7 @@ func (r *rpcStream) Close() error {
// send the end of stream message
if r.sendEOS {
// no need to check for error
//nolint:errcheck,gosec
r.codec.Write(&codec.Message{
Id: r.id,
Target: r.request.Service(),
@@ -164,10 +175,13 @@ func (r *rpcStream) Close() error {
err := r.codec.Close()
rerr := r.Error()
if r.close && rerr == nil {
rerr = errors.New("connection header set to close")
}
// release the connection
r.release(r.Error())
r.release(rerr)
// return the codec error
return err
}
}
+4 -4
View File
@@ -6,14 +6,14 @@ import (
"go-micro.dev/v4/registry"
)
// CallFunc represents the individual call func
// CallFunc represents the individual call func.
type CallFunc func(ctx context.Context, node *registry.Node, req Request, rsp interface{}, opts CallOptions) error
// CallWrapper is a low level wrapper for the CallFunc
// CallWrapper is a low level wrapper for the CallFunc.
type CallWrapper func(CallFunc) CallFunc
// Wrapper wraps a client and returns a client
// Wrapper wraps a client and returns a client.
type Wrapper func(Client) Client
// StreamWrapper wraps a Stream and returns the equivalent
// StreamWrapper wraps a Stream and returns the equivalent.
type StreamWrapper func(Stream) Stream
+1 -1
View File
@@ -12,7 +12,7 @@ type Codec struct {
Conn io.ReadWriteCloser
}
// Frame gives us the ability to define raw data to send over the pipes
// Frame gives us the ability to define raw data to send over the pipes.
type Frame struct {
Data []byte
}
+6 -5
View File
@@ -19,7 +19,7 @@ var (
type MessageType int
// Takes in a connection/buffer and returns a new Codec
// Takes in a connection/buffer and returns a new Codec.
type NewCodec func(io.ReadWriteCloser) Codec
// Codec encodes/decodes various types of messages used within go-micro.
@@ -55,14 +55,15 @@ type Marshaler interface {
// the communication, likely followed by the body.
// In the case of an error, body may be nil.
type Message struct {
// The values read from the socket
Header map[string]string
Id string
Type MessageType
Target string
Method string
Endpoint string
Error string
// The values read from the socket
Header map[string]string
Body []byte
Body []byte
Type MessageType
}
+4 -3
View File
@@ -10,6 +10,7 @@ import (
"github.com/golang/protobuf/proto"
"go-micro.dev/v4/codec"
"go-micro.dev/v4/transport/headers"
)
type Codec struct {
@@ -29,8 +30,8 @@ func (c *Codec) ReadHeader(m *codec.Message, t codec.MessageType) error {
// service method
path := m.Header[":path"]
if len(path) == 0 || path[0] != '/' {
m.Target = m.Header["Micro-Service"]
m.Endpoint = m.Header["Micro-Endpoint"]
m.Target = m.Header[headers.Request]
m.Endpoint = m.Header[headers.Endpoint]
} else {
// [ , a.package.Foo, Bar]
parts := strings.Split(path, "/")
@@ -89,7 +90,7 @@ func (c *Codec) Write(m *codec.Message, b interface{}) error {
m.Header[":authority"] = m.Target
m.Header["content-type"] = c.ContentType
case codec.Response:
m.Header["Trailer"] = "grpc-status" //, grpc-message"
m.Header["Trailer"] = "grpc-status" // , grpc-message"
m.Header["content-type"] = c.ContentType
m.Header[":status"] = "200"
m.Header["grpc-status"] = "0"
+1 -1
View File
@@ -11,7 +11,7 @@ import (
var jsonpbMarshaler = &jsonpb.Marshaler{}
// create buffer pool with 16 instances each preallocated with 256 bytes
// create buffer pool with 16 instances each preallocated with 256 bytes.
var bufferPool = bpool.NewSizedBufferPool(16, 256)
type Marshaler struct{}
+7 -5
View File
@@ -10,22 +10,24 @@ import (
)
type clientCodec struct {
dec *json.Decoder // for reading JSON values
enc *json.Encoder // for writing JSON values
c io.Closer
// temporary work space
req clientRequest
resp clientResponse
sync.Mutex
c io.Closer
dec *json.Decoder // for reading JSON values
enc *json.Encoder // for writing JSON values
pending map[interface{}]string
sync.Mutex
}
type clientRequest struct {
Method string `json:"method"`
Params [1]interface{} `json:"params"`
ID interface{} `json:"id"`
Method string `json:"method"`
}
type clientResponse struct {
+5 -5
View File
@@ -11,11 +11,11 @@ import (
)
type jsonCodec struct {
buf *bytes.Buffer
mt codec.MessageType
rwc io.ReadWriteCloser
buf *bytes.Buffer
c *clientCodec
s *serverCodec
mt codec.MessageType
}
func (j *jsonCodec) Close() error {
@@ -41,7 +41,7 @@ func (j *jsonCodec) Write(m *codec.Message, b interface{}) error {
_, err = j.rwc.Write(data)
return err
default:
return fmt.Errorf("Unrecognised message type: %v", m.Type)
return fmt.Errorf("Unrecognized message type: %v", m.Type)
}
}
@@ -58,7 +58,7 @@ func (j *jsonCodec) ReadHeader(m *codec.Message, mt codec.MessageType) error {
_, err := io.Copy(j.buf, j.rwc)
return err
default:
return fmt.Errorf("Unrecognised message type: %v", mt)
return fmt.Errorf("Unrecognized message type: %v", mt)
}
}
@@ -73,7 +73,7 @@ func (j *jsonCodec) ReadBody(b interface{}) error {
return json.Unmarshal(j.buf.Bytes(), b)
}
default:
return fmt.Errorf("Unrecognised message type: %v", j.mt)
return fmt.Errorf("Unrecognized message type: %v", j.mt)
}
return nil
}
+2 -2
View File
@@ -19,9 +19,9 @@ type serverCodec struct {
}
type serverRequest struct {
Method string `json:"method"`
Params *json.RawMessage `json:"params"`
ID interface{} `json:"id"`
Params *json.RawMessage `json:"params"`
Method string `json:"method"`
}
type serverResponse struct {
+1 -1
View File
@@ -8,7 +8,7 @@ import (
"go-micro.dev/v4/codec"
)
// create buffer pool with 16 instances each preallocated with 256 bytes
// create buffer pool with 16 instances each preallocated with 256 bytes.
var bufferPool = bpool.NewSizedBufferPool(16, 256)
type Marshaler struct{}
+5 -5
View File
@@ -17,10 +17,10 @@ type flusher interface {
}
type protoCodec struct {
sync.Mutex
rwc io.ReadWriteCloser
mt codec.MessageType
buf *bytes.Buffer
mt codec.MessageType
sync.Mutex
}
func (c *protoCodec) Close() error {
@@ -114,7 +114,7 @@ func (c *protoCodec) Write(m *codec.Message, b interface{}) error {
}
c.rwc.Write(data)
default:
return fmt.Errorf("Unrecognised message type: %v", m.Type)
return fmt.Errorf("Unrecognized message type: %v", m.Type)
}
return nil
}
@@ -153,7 +153,7 @@ func (c *protoCodec) ReadHeader(m *codec.Message, mt codec.MessageType) error {
_, err := io.Copy(c.buf, c.rwc)
return err
default:
return fmt.Errorf("Unrecognised message type: %v", mt)
return fmt.Errorf("Unrecognized message type: %v", mt)
}
return nil
}
@@ -170,7 +170,7 @@ func (c *protoCodec) ReadBody(b interface{}) error {
case codec.Event:
data = c.buf.Bytes()
default:
return fmt.Errorf("Unrecognised message type: %v", c.mt)
return fmt.Errorf("Unrecognized message type: %v", c.mt)
}
if b != nil {
return proto.Unmarshal(data, b.(proto.Message))
+12 -12
View File
@@ -2,23 +2,23 @@
Config is a pluggable dynamic config package
Most config in applications are statically configured or include complex logic to load from multiple sources.
Most config in applications are statically configured or include complex logic to load from multiple sources.
Go Config makes this easy, pluggable and mergeable. You'll never have to deal with config in the same way again.
## Features
- **Dynamic Loading** - Load configuration from multiple source as and when needed. Go Config manages watching config sources
in the background and automatically merges and updates an in memory view.
- **Dynamic Loading** - Load configuration from multiple source as and when needed. Go Config manages watching config sources
in the background and automatically merges and updates an in memory view.
- **Pluggable Sources** - Choose from any number of sources to load and merge config. The backend source is abstracted away into
a standard format consumed internally and decoded via encoders. Sources can be env vars, flags, file, etcd, k8s configmap, etc.
- **Pluggable Sources** - Choose from any number of sources to load and merge config. The backend source is abstracted away into
a standard format consumed internally and decoded via encoders. Sources can be env vars, flags, file, etcd, k8s configmap, etc.
- **Mergeable Config** - If you specify multiple sources of config, regardless of format, they will be merged and presented in
a single view. This massively simplifies priority order loading and changes based on environment.
- **Mergeable Config** - If you specify multiple sources of config, regardless of format, they will be merged and presented in
a single view. This massively simplifies priority order loading and changes based on environment.
- **Observe Changes** - Optionally watch the config for changes to specific values. Hot reload your app using Go Config's watcher.
You don't have to handle ad-hoc hup reloading or whatever else, just keep reading the config and watch for changes if you need
to be notified.
- **Observe Changes** - Optionally watch the config for changes to specific values. Hot reload your app using Go Config's watcher.
You don't have to handle ad-hoc hup reloading or whatever else, just keep reading the config and watch for changes if you need
to be notified.
- **Sane Defaults** - In case config loads badly or is completely wiped away for some unknown reason, you can specify fallback
values when accessing any config values directly. This ensures you'll always be reading some sane default in the event of a problem.
- **Sane Defaults** - In case config loads badly or is completely wiped away for some unknown reason, you can specify fallback
values when accessing any config values directly. This ensures you'll always be reading some sane default in the event of a problem.
+14 -13
View File
@@ -10,7 +10,7 @@ import (
"go-micro.dev/v4/config/source/file"
)
// Config is an interface abstraction for dynamic configuration
// Config is an interface abstraction for dynamic configuration.
type Config interface {
// provide the reader.Values interface
reader.Values
@@ -28,7 +28,7 @@ type Config interface {
Watch(path ...string) (Watcher, error)
}
// Watcher is the config watcher
// Watcher is the config watcher.
type Watcher interface {
Next() (reader.Value, error)
Stop() error
@@ -37,62 +37,63 @@ type Watcher interface {
type Options struct {
Loader loader.Loader
Reader reader.Reader
Source []source.Source
// for alternative data
Context context.Context
Source []source.Source
WithWatcherDisabled bool
}
type Option func(o *Options)
var (
// Default Config Manager
// Default Config Manager.
DefaultConfig, _ = NewConfig()
)
// NewConfig returns new config
// NewConfig returns new config.
func NewConfig(opts ...Option) (Config, error) {
return newConfig(opts...)
}
// Return config as raw json
// Return config as raw json.
func Bytes() []byte {
return DefaultConfig.Bytes()
}
// Return config as a map
// Return config as a map.
func Map() map[string]interface{} {
return DefaultConfig.Map()
}
// Scan values to a go type
// Scan values to a go type.
func Scan(v interface{}) error {
return DefaultConfig.Scan(v)
}
// Force a source changeset sync
// Force a source changeset sync.
func Sync() error {
return DefaultConfig.Sync()
}
// Get a value from the config
// Get a value from the config.
func Get(path ...string) reader.Value {
return DefaultConfig.Get(path...)
}
// Load config sources
// Load config sources.
func Load(source ...source.Source) error {
return DefaultConfig.Load(source...)
}
// Watch a value for changes
// Watch a value for changes.
func Watch(path ...string) (Watcher, error) {
return DefaultConfig.Watch(path...)
}
// LoadFile is short hand for creating a file source and loading it
// LoadFile is short hand for creating a file source and loading it.
func LoadFile(path string) error {
return Load(file.NewSource(
file.WithPath(path),
+6 -6
View File
@@ -13,21 +13,21 @@ import (
)
type config struct {
// the current values
vals reader.Values
exit chan bool
// the current snapshot
snap *loader.Snapshot
opts Options
sync.RWMutex
// the current snapshot
snap *loader.Snapshot
// the current values
vals reader.Values
}
type watcher struct {
lw loader.Watcher
rd reader.Reader
path []string
value reader.Value
path []string
}
func newConfig(opts ...Option) (Config, error) {
@@ -159,7 +159,7 @@ func (c *config) Scan(v interface{}) error {
return c.vals.Scan(v)
}
// sync loads all the sources, calls the parser and updates the config
// sync loads all the sources, calls the parser and updates the config.
func (c *config) Sync() error {
if err := c.opts.Loader.Sync(); err != nil {
return err
+9 -6
View File
@@ -1,4 +1,4 @@
// package loader manages loading from multiple sources
// Package loader manages loading from multiple sources
package loader
import (
@@ -8,7 +8,7 @@ import (
"go-micro.dev/v4/config/source"
)
// Loader manages loading sources
// Loader manages loading sources.
type Loader interface {
// Stop the loader
Close() error
@@ -24,7 +24,7 @@ type Loader interface {
String() string
}
// Watcher lets you watch sources and returns a merged ChangeSet
// Watcher lets you watch sources and returns a merged ChangeSet.
type Watcher interface {
// First call to next may return the current Snapshot
// If you are watching a path then only the data from
@@ -34,7 +34,7 @@ type Watcher interface {
Stop() error
}
// Snapshot is a merged ChangeSet
// Snapshot is a merged ChangeSet.
type Snapshot struct {
// The merged ChangeSet
ChangeSet *source.ChangeSet
@@ -42,19 +42,22 @@ type Snapshot struct {
Version string
}
// Options contains all options for a config loader.
type Options struct {
Reader reader.Reader
Source []source.Source
// for alternative data
Context context.Context
Source []source.Source
WithWatcherDisabled bool
}
// Option is a helper for a single option.
type Option func(o *Options)
// Copy snapshot
// Copy snapshot.
func Copy(s *Snapshot) *Snapshot {
cs := *(s.ChangeSet)
+25 -20
View File
@@ -7,6 +7,7 @@ import (
"fmt"
"strings"
"sync"
"sync/atomic"
"time"
"go-micro.dev/v4/config/loader"
@@ -16,34 +17,39 @@ import (
)
type memory struct {
exit chan bool
opts loader.Options
sync.RWMutex
// the current snapshot
snap *loader.Snapshot
// the current values
vals reader.Values
exit chan bool
// the current snapshot
snap *loader.Snapshot
watchers *list.List
opts loader.Options
// all the sets
sets []*source.ChangeSet
// all the sources
sources []source.Source
watchers *list.List
sync.RWMutex
}
type updateValue struct {
version string
value reader.Value
version string
}
type watcher struct {
exit chan bool
path []string
value reader.Value
reader reader.Reader
version string
version atomic.Value
exit chan bool
updates chan updateValue
path []string
}
func (w *watcher) getVersion() string {
return w.version.Load().(string)
}
func (m *memory) watch(idx int, s source.Source) {
@@ -128,7 +134,7 @@ func (m *memory) loaded() bool {
return loaded
}
// reload reads the sets and creates new values
// reload reads the sets and creates new values.
func (m *memory) reload() error {
m.Lock()
@@ -169,7 +175,7 @@ func (m *memory) update() {
m.RUnlock()
for _, w := range watchers {
if w.version >= snap.Version {
if w.getVersion() >= snap.Version {
continue
}
@@ -185,7 +191,7 @@ func (m *memory) update() {
}
}
// Snapshot returns a snapshot of the current loaded config
// Snapshot returns a snapshot of the current loaded config.
func (m *memory) Snapshot() (*loader.Snapshot, error) {
if m.loaded() {
m.RLock()
@@ -207,7 +213,7 @@ func (m *memory) Snapshot() (*loader.Snapshot, error) {
return snap, nil
}
// Sync loads all the sources, calls the parser and updates the config
// Sync loads all the sources, calls the parser and updates the config.
func (m *memory) Sync() error {
//nolint:prealloc
var sets []*source.ChangeSet
@@ -357,8 +363,8 @@ func (m *memory) Watch(path ...string) (loader.Watcher, error) {
value: value,
reader: m.opts.Reader,
updates: make(chan updateValue, 1),
version: m.snap.Version,
}
w.version.Store(m.snap.Version)
e := m.watchers.PushBack(w)
@@ -392,9 +398,8 @@ func (w *watcher) Next() (*loader.Snapshot, error) {
return &loader.Snapshot{
ChangeSet: cs,
Version: w.version,
Version: w.getVersion(),
}
}
for {
@@ -403,13 +408,13 @@ func (w *watcher) Next() (*loader.Snapshot, error) {
return nil, errors.New("watcher stopped")
case uv := <-w.updates:
if uv.version <= w.version {
if uv.version <= w.getVersion() {
continue
}
v := uv.value
w.version = uv.version
w.version.Store(uv.version)
if bytes.Equal(w.value.Bytes(), v.Bytes()) {
continue
+2 -2
View File
@@ -6,14 +6,14 @@ import (
"go-micro.dev/v4/config/source"
)
// WithSource appends a source to list of sources
// WithSource appends a source to list of sources.
func WithSource(s source.Source) loader.Option {
return func(o *loader.Options) {
o.Source = append(o.Source, s)
}
}
// WithReader sets the config reader
// WithReader sets the config reader.
func WithReader(r reader.Reader) loader.Option {
return func(o *loader.Options) {
o.Reader = r
+3 -3
View File
@@ -6,21 +6,21 @@ import (
"go-micro.dev/v4/config/source"
)
// WithLoader sets the loader for manager config
// WithLoader sets the loader for manager config.
func WithLoader(l loader.Loader) Option {
return func(o *Options) {
o.Loader = l
}
}
// WithSource appends a source to list of sources
// WithSource appends a source to list of sources.
func WithSource(s source.Source) Option {
return func(o *Options) {
o.Source = append(o.Source, s)
}
}
// WithReader sets the config reader
// WithReader sets the config reader.
func WithReader(r reader.Reader) Option {
return func(o *Options) {
o.Reader = r
+1 -1
View File
@@ -73,7 +73,7 @@ func (j *jsonReader) String() string {
return "json"
}
// NewReader creates a json reader
// NewReader creates a json reader.
func NewReader(opts ...reader.Option) reader.Reader {
options := reader.NewOptions(opts...)
return &jsonReader{
+1 -1
View File
@@ -190,7 +190,7 @@ func (j *jsonValue) Scan(v interface{}) error {
func (j *jsonValue) Bytes() []byte {
b, err := j.Json.Bytes()
if err != nil {
// try return marshalled
// try return marshaled
b, err = j.Json.MarshalJSON()
if err != nil {
return []byte{}
+3 -3
View File
@@ -7,14 +7,14 @@ import (
"go-micro.dev/v4/config/source"
)
// Reader is an interface for merging changesets
// Reader is an interface for merging changesets.
type Reader interface {
Merge(...*source.ChangeSet) (*source.ChangeSet, error)
Values(*source.ChangeSet) (Values, error)
String() string
}
// Values is returned by the reader
// Values is returned by the reader.
type Values interface {
Bytes() []byte
Get(path ...string) Value
@@ -24,7 +24,7 @@ type Values interface {
Scan(v interface{}) error
}
// Value represents a value of any type
// Value represents a value of any type.
type Value interface {
Bool(def bool) bool
Int(def int) int
+1 -1
View File
@@ -5,7 +5,7 @@ import (
"fmt"
)
// Sum returns the md5 checksum of the ChangeSet data
// Sum returns the md5 checksum of the ChangeSet data.
func (c *ChangeSet) Sum() string {
h := md5.New()
h.Write(c.Data)
+15 -16
View File
@@ -1,9 +1,9 @@
# File Source
The file source reads config from a file.
The file source reads config from a file.
It uses the File extension to determine the Format e.g `config.yaml` has the yaml format.
It does not make use of encoders or interpet the file data. If a file extension is not present
It uses the File extension to determine the Format e.g `config.yaml` has the yaml format.
It does not make use of encoders or interpet the file data. If a file extension is not present
the source Format will default to the Encoder in options.
## Example
@@ -12,16 +12,16 @@ A config file format in json
```json
{
"hosts": {
"database": {
"address": "10.0.0.1",
"port": 3306
},
"cache": {
"address": "10.0.0.2",
"port": 6379
}
"hosts": {
"database": {
"address": "10.0.0.1",
"port": 3306
},
"cache": {
"address": "10.0.0.2",
"port": 6379
}
}
}
```
@@ -39,7 +39,7 @@ fileSource := file.NewSource(
To load different file formats e.g yaml, toml, xml simply specify them with their extension
```
```go
fileSource := file.NewSource(
file.WithPath("/tmp/config.yaml"),
)
@@ -47,12 +47,12 @@ fileSource := file.NewSource(
If you want to specify a file without extension, ensure you set the encoder to the same format
```
```go
e := toml.NewEncoder()
fileSource := file.NewSource(
file.WithPath("/tmp/config"),
source.WithEncoder(e),
source.WithEncoder(e),
)
```
@@ -67,4 +67,3 @@ conf := config.NewConfig()
// Load file source
conf.Load(fileSource)
```
+1 -1
View File
@@ -10,9 +10,9 @@ import (
)
type file struct {
opts source.Options
fs fs.FS
path string
opts source.Options
}
var (
+2 -2
View File
@@ -10,7 +10,7 @@ import (
type filePathKey struct{}
type fsKey struct{}
// WithPath sets the path to file
// WithPath sets the path to file.
func WithPath(p string) source.Option {
return func(o *source.Options) {
if o.Context == nil {
@@ -20,7 +20,7 @@ func WithPath(p string) source.Option {
}
}
// WithFS sets the underlying filesystem to lookup file from (default os.FS)
// WithFS sets the underlying filesystem to lookup file from (default os.FS).
func WithFS(fs fs.FS) source.Option {
return func(o *source.Options) {
if o.Context == nil {
+17 -17
View File
@@ -13,8 +13,7 @@ import (
type watcher struct {
f *file
fw *fsnotify.Watcher
exit chan bool
fw *fsnotify.Watcher
}
func newWatcher(f *file) (source.Watcher, error) {
@@ -26,24 +25,21 @@ func newWatcher(f *file) (source.Watcher, error) {
fw.Add(f.path)
return &watcher{
f: f,
fw: fw,
exit: make(chan bool),
f: f,
fw: fw,
}, nil
}
func (w *watcher) Next() (*source.ChangeSet, error) {
// is it closed?
select {
case <-w.exit:
return nil, source.ErrWatcherStopped
default:
}
// try get the event
select {
case event, _ := <-w.fw.Events:
if event.Op == fsnotify.Rename {
case event, ok := <-w.fw.Events:
// check if channel was closed (i.e. Watcher.Close() was called).
if !ok {
return nil, source.ErrWatcherStopped
}
if event.Has(fsnotify.Rename) {
// check existence of file, and add watch again
_, err := os.Stat(event.Name)
if err == nil || os.IsExist(err) {
@@ -55,11 +51,15 @@ func (w *watcher) Next() (*source.ChangeSet, error) {
if err != nil {
return nil, err
}
return c, nil
case err := <-w.fw.Errors:
case err, ok := <-w.fw.Errors:
// check if channel was closed (i.e. Watcher.Close() was called).
if !ok {
return nil, source.ErrWatcherStopped
}
return nil, err
case <-w.exit:
return nil, source.ErrWatcherStopped
}
}
+16 -17
View File
@@ -13,8 +13,7 @@ import (
type watcher struct {
f *file
fw *fsnotify.Watcher
exit chan bool
fw *fsnotify.Watcher
}
func newWatcher(f *file) (source.Watcher, error) {
@@ -26,24 +25,21 @@ func newWatcher(f *file) (source.Watcher, error) {
fw.Add(f.path)
return &watcher{
f: f,
fw: fw,
exit: make(chan bool),
f: f,
fw: fw,
}, nil
}
func (w *watcher) Next() (*source.ChangeSet, error) {
// is it closed?
select {
case <-w.exit:
return nil, source.ErrWatcherStopped
default:
}
// try get the event
select {
case event := <-w.fw.Events:
if event.Op == fsnotify.Rename {
case event, ok := <-w.fw.Events:
// check if channel was closed (i.e. Watcher.Close() was called).
if !ok {
return nil, source.ErrWatcherStopped
}
if event.Has(fsnotify.Rename) {
// check existence of file, and add watch again
_, err := os.Stat(event.Name)
if err == nil || os.IsExist(err) {
@@ -60,10 +56,13 @@ func (w *watcher) Next() (*source.ChangeSet, error) {
w.fw.Add(w.f.path)
return c, nil
case err := <-w.fw.Errors:
case err, ok := <-w.fw.Errors:
// check if channel was closed (i.e. Watcher.Close() was called).
if !ok {
return nil, source.ErrWatcherStopped
}
return nil, err
case <-w.exit:
return nil, source.ErrWatcherStopped
}
}
+2 -2
View File
@@ -35,14 +35,14 @@ func NewOptions(opts ...Option) Options {
return options
}
// WithEncoder sets the source encoder
// WithEncoder sets the source encoder.
func WithEncoder(e encoder.Encoder) Option {
return func(o *Options) {
o.Encoder = e
}
}
// WithClient sets the source client
// WithClient sets the source client.
func WithClient(c client.Client) Option {
return func(o *Options) {
o.Client = c
+6 -6
View File
@@ -7,11 +7,11 @@ import (
)
var (
// ErrWatcherStopped is returned when source watcher has been stopped
// ErrWatcherStopped is returned when source watcher has been stopped.
ErrWatcherStopped = errors.New("watcher stopped")
)
// Source is the source from which config is loaded
// Source is the source from which config is loaded.
type Source interface {
Read() (*ChangeSet, error)
Write(*ChangeSet) error
@@ -19,16 +19,16 @@ type Source interface {
String() string
}
// ChangeSet represents a set of changes from a source
// ChangeSet represents a set of changes from a source.
type ChangeSet struct {
Data []byte
Timestamp time.Time
Checksum string
Format string
Source string
Timestamp time.Time
Data []byte
}
// Watcher watches a source for changes
// Watcher watches a source for changes.
type Watcher interface {
Next() (*ChangeSet, error)
Stop() error
+9 -9
View File
@@ -8,15 +8,15 @@ import (
)
var (
// Default buffer size if any
// Default buffer size if any.
DefaultSize = 1024
// DefaultLog logger
// DefaultLog logger.
DefaultLog = NewLog()
// Default formatter
// Default formatter.
DefaultFormat = TextFormat
)
// Log is debug log interface for reading and writing logs
// Log is debug log interface for reading and writing logs.
type Log interface {
// Read reads log entries from the logger
Read(...ReadOption) ([]Record, error)
@@ -26,7 +26,7 @@ type Log interface {
Stream() (Stream, error)
}
// Record is log record entry
// Record is log record entry.
type Record struct {
// Timestamp of logged event
Timestamp time.Time `json:"timestamp"`
@@ -36,22 +36,22 @@ type Record struct {
Message interface{} `json:"message"`
}
// Stream returns a log stream
// Stream returns a log stream.
type Stream interface {
Chan() <-chan Record
Stop() error
}
// Format is a function which formats the output
// Format is a function which formats the output.
type FormatFunc func(Record) string
// TextFormat returns text format
// 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
// JSONFormat is a json Format func.
func JSONFormat(r Record) string {
b, _ := json.Marshal(r)
return string(b)
+11 -11
View File
@@ -2,27 +2,27 @@ package log
import "time"
// Option used by the logger
// Option used by the logger.
type Option func(*Options)
// Options are logger options
// Options are logger options.
type Options struct {
// Format specifies the output format
Format FormatFunc
// 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
// Name of the log.
func Name(n string) Option {
return func(o *Options) {
o.Name = n
}
}
// Size sets the size of the ring buffer
// Size sets the size of the ring buffer.
func Size(s int) Option {
return func(o *Options) {
o.Size = s
@@ -35,14 +35,14 @@ func Format(f FormatFunc) Option {
}
}
// DefaultOptions returns default options
// DefaultOptions returns default options.
func DefaultOptions() Options {
return Options{
Size: DefaultSize,
}
}
// ReadOptions for querying the logs
// ReadOptions for querying the logs.
type ReadOptions struct {
// Since what time in past to return the logs
Since time.Time
@@ -52,17 +52,17 @@ type ReadOptions struct {
Stream bool
}
// ReadOption used for reading the logs
// ReadOption used for reading the logs.
type ReadOption func(*ReadOptions)
// Since sets the time since which to return the log records
// 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
// Count sets the number of log records to return.
func Count(c int) ReadOption {
return func(o *ReadOptions) {
o.Count = c
+7 -7
View File
@@ -7,21 +7,21 @@ import (
"go-micro.dev/v4/util/ring"
)
// Should stream from OS
// Should stream from OS.
type osLog struct {
format FormatFunc
once sync.Once
sync.RWMutex
buffer *ring.Buffer
subs map[string]*osStream
sync.RWMutex
once sync.Once
}
type osStream struct {
stream chan Record
}
// Read reads log entries from the logger
// Read reads log entries from the logger.
func (o *osLog) Read(...ReadOption) ([]Record, error) {
var records []Record
@@ -33,13 +33,13 @@ func (o *osLog) Read(...ReadOption) ([]Record, error) {
return records, nil
}
// Write writes records to log
// Write writes records to log.
func (o *osLog) Write(r Record) error {
o.buffer.Put(r)
return nil
}
// Stream log records
// Stream log records.
func (o *osLog) Stream() (Stream, error) {
o.Lock()
defer o.Unlock()
+3 -3
View File
@@ -11,16 +11,16 @@ import (
)
type httpProfile struct {
server *http.Server
sync.Mutex
running bool
server *http.Server
}
var (
DefaultAddress = ":6060"
)
// Start the profiler
// Start the profiler.
func (h *httpProfile) Start() error {
h.Lock()
defer h.Unlock()
@@ -42,7 +42,7 @@ func (h *httpProfile) Start() error {
return nil
}
// Stop the profiler
// Stop the profiler.
func (h *httpProfile) Stop() error {
h.Lock()
defer h.Unlock()
+4 -4
View File
@@ -12,15 +12,15 @@ import (
)
type profiler struct {
opts profile.Options
sync.Mutex
running bool
// where the cpu profile is written
cpuFile *os.File
// where the mem profile is written
memFile *os.File
opts profile.Options
sync.Mutex
running bool
}
func (p *profiler) Start() error {
+1 -1
View File
@@ -35,7 +35,7 @@ type Options struct {
type Option func(o *Options)
// Name of the profile
// Name of the profile.
func Name(n string) Option {
return func(o *Options) {
o.Name = n
+1 -1
View File
@@ -9,10 +9,10 @@ import (
)
type memTracer struct {
opts Options
// ring buffer of traces
buffer *ring.Buffer
opts Options
}
func (t *memTracer) Read(opts ...ReadOption) ([]*Span, error) {
+21
View File
@@ -0,0 +1,21 @@
package trace
import "context"
type noop struct{}
func (n *noop) Init(...Option) error {
return nil
}
func (n *noop) Start(ctx context.Context, name string) (context.Context, *Span) {
return nil, nil
}
func (n *noop) Finish(*Span) error {
return nil
}
func (n *noop) Read(...ReadOption) ([]*Span, error) {
return nil, nil
}
+3 -3
View File
@@ -14,7 +14,7 @@ type ReadOptions struct {
type ReadOption func(o *ReadOptions)
// Read the given trace
// Read the given trace.
func ReadTrace(t string) ReadOption {
return func(o *ReadOptions) {
o.Trace = t
@@ -22,11 +22,11 @@ func ReadTrace(t string) ReadOption {
}
const (
// DefaultSize of the buffer
// DefaultSize of the buffer.
DefaultSize = 64
)
// DefaultOptions returns default options
// DefaultOptions returns default options.
func DefaultOptions() Options {
return Options{
Size: DefaultSize,
+26 -43
View File
@@ -6,9 +6,15 @@ import (
"time"
"go-micro.dev/v4/metadata"
"go-micro.dev/v4/transport/headers"
)
// Tracer is an interface for distributed tracing
var (
// DefaultTracer is the default tracer.
DefaultTracer = NewTracer()
)
// Tracer is an interface for distributed tracing.
type Tracer interface {
// Start a trace
Start(ctx context.Context, name string) (context.Context, *Span)
@@ -18,18 +24,22 @@ type Tracer interface {
Read(...ReadOption) ([]*Span, error)
}
// SpanType describe the nature of the trace span
// SpanType describe the nature of the trace span.
type SpanType int
const (
// SpanTypeRequestInbound is a span created when serving a request
// SpanTypeRequestInbound is a span created when serving a request.
SpanTypeRequestInbound SpanType = iota
// SpanTypeRequestOutbound is a span created when making a service call
// SpanTypeRequestOutbound is a span created when making a service call.
SpanTypeRequestOutbound
)
// Span is used to record an entry
// Span is used to record an entry.
type Span struct {
// Start time
Started time.Time
// associated data
Metadata map[string]string
// Id of the trace
Trace string
// name of the span
@@ -38,62 +48,35 @@ type Span struct {
Id string
// parent span id
Parent string
// Start time
Started time.Time
// Duration in nano seconds
Duration time.Duration
// associated data
Metadata map[string]string
// Type
Type SpanType
}
const (
traceIDKey = "Micro-Trace-Id"
spanIDKey = "Micro-Span-Id"
)
// FromContext returns a span from context
// FromContext returns a span from context.
func FromContext(ctx context.Context) (traceID string, parentSpanID string, isFound bool) {
traceID, traceOk := metadata.Get(ctx, traceIDKey)
microID, microOk := metadata.Get(ctx, "Micro-Id")
traceID, traceOk := metadata.Get(ctx, headers.TraceIDKey)
microID, microOk := metadata.Get(ctx, headers.ID)
if !traceOk && !microOk {
isFound = false
return
}
if !traceOk {
traceID = microID
}
parentSpanID, ok := metadata.Get(ctx, spanIDKey)
parentSpanID, ok := metadata.Get(ctx, headers.SpanID)
return traceID, parentSpanID, ok
}
// ToContext saves the trace and span ids in the context
// ToContext saves the trace and span ids in the context.
func ToContext(ctx context.Context, traceID, parentSpanID string) context.Context {
return metadata.MergeContext(ctx, map[string]string{
traceIDKey: traceID,
spanIDKey: parentSpanID,
headers.TraceIDKey: traceID,
headers.SpanID: parentSpanID,
}, true)
}
var (
DefaultTracer Tracer = NewTracer()
)
type noop struct{}
func (n *noop) Init(...Option) error {
return nil
}
func (n *noop) Start(ctx context.Context, name string) (context.Context, *Span) {
return nil, nil
}
func (n *noop) Finish(*Span) error {
return nil
}
func (n *noop) Read(...ReadOption) ([]*Span, error) {
return nil, nil
}
+5 -5
View File
@@ -117,7 +117,7 @@ func InternalServerError(id, format string, a ...interface{}) error {
}
}
// Equal tries to compare errors
// Equal tries to compare errors.
func Equal(err1 error, err2 error) bool {
verr1, ok1 := err1.(*Error)
verr2, ok2 := err2.(*Error)
@@ -137,7 +137,7 @@ func Equal(err1 error, err2 error) bool {
return true
}
// FromError try to convert go error to *Error
// FromError try to convert go error to *Error.
func FromError(err error) *Error {
if err == nil {
return nil
@@ -149,7 +149,7 @@ func FromError(err error) *Error {
return Parse(err.Error())
}
// As finds the first error in err's chain that matches *Error
// As finds the first error in err's chain that matches *Error.
func As(err error) (*Error, bool) {
if err == nil {
return nil, false
@@ -167,8 +167,8 @@ func NewMultiError() *MultiError {
}
}
func (e *MultiError) Append(err *Error) {
e.Errors = append(e.Errors, err)
func (e *MultiError) Append(err ...*Error) {
e.Errors = append(e.Errors, err...)
}
func (e *MultiError) HasErrors() bool {
+19 -19
View File
@@ -8,26 +8,26 @@ import (
)
var (
// DefaultStream is the default events stream implementation
// DefaultStream is the default events stream implementation.
DefaultStream Stream
// DefaultStore is the default events store implementation
// DefaultStore is the default events store implementation.
DefaultStore Store
)
var (
// ErrMissingTopic is returned if a blank topic was provided to publish
// ErrMissingTopic is returned if a blank topic was provided to publish.
ErrMissingTopic = errors.New("Missing topic")
// ErrEncodingMessage is returned from publish if there was an error encoding the message option
// ErrEncodingMessage is returned from publish if there was an error encoding the message option.
ErrEncodingMessage = errors.New("Error encoding message")
)
// Stream is an event streaming interface
// Stream is an event streaming interface.
type Stream interface {
Publish(topic string, msg interface{}, opts ...PublishOption) error
Consume(topic string, opts ...ConsumeOption) (<-chan Event, error)
}
// Store is an event store interface
// Store is an event store interface.
type Store interface {
Read(topic string, opts ...ReadOption) ([]*Event, error)
Write(event *Event, opts ...WriteOption) error
@@ -36,29 +36,29 @@ type Store interface {
type AckFunc func() error
type NackFunc func() error
// Event is the object returned by the broker when you subscribe to a topic
// Event is the object returned by the broker when you subscribe to a topic.
type Event struct {
// ID to uniquely identify the event
ID string
// Topic of event, e.g. "registry.service.created"
Topic string
// Timestamp of the event
Timestamp time.Time
// Metadata contains the values the event was indexed by
Metadata map[string]string
// Payload contains the encoded message
Payload []byte
ackFunc AckFunc
nackFunc NackFunc
// ID to uniquely identify the event
ID string
// Topic of event, e.g. "registry.service.created"
Topic string
// Payload contains the encoded message
Payload []byte
}
// Unmarshal the events message into an object
// Unmarshal the events message into an object.
func (e *Event) Unmarshal(v interface{}) error {
return json.Unmarshal(e.Payload, v)
}
// Ack acknowledges successful processing of the event in ManualAck mode
// Ack acknowledges successful processing of the event in ManualAck mode.
func (e *Event) Ack() error {
return e.ackFunc()
}
@@ -67,7 +67,7 @@ func (e *Event) SetAckFunc(f AckFunc) {
e.ackFunc = f
}
// Nack negatively acknowledges processing of the event (i.e. failure) in ManualAck mode
// Nack negatively acknowledges processing of the event (i.e. failure) in ManualAck mode.
func (e *Event) Nack() error {
return e.nackFunc()
}
@@ -76,17 +76,17 @@ func (e *Event) SetNackFunc(f NackFunc) {
e.nackFunc = f
}
// Publish an event to a topic
// Publish an event to a topic.
func Publish(topic string, msg interface{}, opts ...PublishOption) error {
return DefaultStream.Publish(topic, msg, opts...)
}
// Consume to events
// Consume to events.
func Consume(topic string, opts ...ConsumeOption) (<-chan Event, error) {
return DefaultStream.Consume(topic, opts...)
}
// Read events for a topic
// Read events for a topic.
func Read(topic string, opts ...ReadOption) ([]*Event, error) {
return DefaultStore.Read(topic, opts...)
}
+7 -7
View File
@@ -8,12 +8,11 @@ import (
"github.com/google/uuid"
"github.com/pkg/errors"
log "go-micro.dev/v4/logger"
"go-micro.dev/v4/store"
)
// NewStream returns an initialized memory stream
// NewStream returns an initialized memory stream.
func NewStream(opts ...Option) (Stream, error) {
// parse the options
options := NewOptions(opts...)
@@ -22,15 +21,16 @@ func NewStream(opts ...Option) (Stream, error) {
}
type subscriber struct {
Group string
Topic string
Channel chan Event
sync.RWMutex
retryMap map[string]int
Group string
Topic string
retryLimit int
autoAck bool
ackWait time.Duration
sync.RWMutex
autoAck bool
}
type mem struct {
@@ -143,7 +143,7 @@ func (m *mem) Consume(topic string, opts ...ConsumeOption) (<-chan Event, error)
}
// lookupPreviousEvents finds events for a subscriber which occurred before a given time and sends
// them into the subscribers channel
// them into the subscribers channel.
func (m *mem) lookupPreviousEvents(sub *subscriber, startTime time.Time) {
// lookup all events which match the topic (a blank topic will return all results)
recs, err := m.store.Read(sub.Topic+"/", store.ReadPrefix())
+26 -26
View File
@@ -25,21 +25,21 @@ func NewOptions(opts ...Option) *Options {
}
type StoreOptions struct {
TTL time.Duration
Backup Backup
Logger logger.Logger
TTL time.Duration
}
type StoreOption func(o *StoreOptions)
// WithLogger sets the underline logger
// WithLogger sets the underline logger.
func WithLogger(l logger.Logger) StoreOption {
return func(o *StoreOptions) {
o.Logger = l
}
}
// PublishOptions contains all the options which can be provided when publishing an event
// PublishOptions contains all the options which can be provided when publishing an event.
type PublishOptions struct {
// Metadata contains any keys which can be used to query the data, for example a customer id
Metadata map[string]string
@@ -47,55 +47,55 @@ type PublishOptions struct {
Timestamp time.Time
}
// PublishOption sets attributes on PublishOptions
// PublishOption sets attributes on PublishOptions.
type PublishOption func(o *PublishOptions)
// WithMetadata sets the Metadata field on PublishOptions
// WithMetadata sets the Metadata field on PublishOptions.
func WithMetadata(md map[string]string) PublishOption {
return func(o *PublishOptions) {
o.Metadata = md
}
}
// WithTimestamp sets the timestamp field on PublishOptions
// WithTimestamp sets the timestamp field on PublishOptions.
func WithTimestamp(t time.Time) PublishOption {
return func(o *PublishOptions) {
o.Timestamp = t
}
}
// ConsumeOptions contains all the options which can be provided when subscribing to a topic
// ConsumeOptions contains all the options which can be provided when subscribing to a topic.
type ConsumeOptions struct {
// Group is the name of the consumer group, if two consumers have the same group the events
// are distributed between them
Group string
// Offset is the time from which the messages should be consumed from. If not provided then
// the messages will be consumed starting from the moment the Subscription starts.
Offset time.Time
// Group is the name of the consumer group, if two consumers have the same group the events
// are distributed between them
Group string
AckWait time.Duration
// RetryLimit indicates number of times a message is retried
RetryLimit int
// AutoAck if true (default true), automatically acknowledges every message so it will not be redelivered.
// If false specifies that each message need ts to be manually acknowledged by the subscriber.
// If processing is successful the message should be ack'ed to remove the message from the stream.
// If processing is unsuccessful the message should be nack'ed (negative acknowledgement) which will mean it will
// remain on the stream to be processed again.
AutoAck bool
AckWait time.Duration
// RetryLimit indicates number of times a message is retried
RetryLimit int
// CustomRetries indicates whether to use RetryLimit
CustomRetries bool
}
// ConsumeOption sets attributes on ConsumeOptions
// ConsumeOption sets attributes on ConsumeOptions.
type ConsumeOption func(o *ConsumeOptions)
// WithGroup sets the consumer group to be part of when consuming events
// WithGroup sets the consumer group to be part of when consuming events.
func WithGroup(q string) ConsumeOption {
return func(o *ConsumeOptions) {
o.Group = q
}
}
// WithOffset sets the offset time at which to start consuming events
// WithOffset sets the offset time at which to start consuming events.
func WithOffset(t time.Time) ConsumeOption {
return func(o *ConsumeOptions) {
o.Offset = t
@@ -103,7 +103,7 @@ func WithOffset(t time.Time) ConsumeOption {
}
// WithAutoAck sets the AutoAck field on ConsumeOptions and an ackWait duration after which if no ack is received
// the message is requeued in case auto ack is turned off
// the message is requeued in case auto ack is turned off.
func WithAutoAck(ack bool, ackWait time.Duration) ConsumeOption {
return func(o *ConsumeOptions) {
o.AutoAck = ack
@@ -112,7 +112,7 @@ func WithAutoAck(ack bool, ackWait time.Duration) ConsumeOption {
}
// WithRetryLimit sets the RetryLimit field on ConsumeOptions.
// Set to -1 for infinite retries (default)
// Set to -1 for infinite retries (default).
func WithRetryLimit(retries int) ConsumeOption {
return func(o *ConsumeOptions) {
o.RetryLimit = retries
@@ -127,24 +127,24 @@ func (s ConsumeOptions) GetRetryLimit() int {
return s.RetryLimit
}
// WriteOptions contains all the options which can be provided when writing an event to a store
// WriteOptions contains all the options which can be provided when writing an event to a store.
type WriteOptions struct {
// TTL is the duration the event should be recorded for, a zero value TTL indicates the event should
// be stored indefinately
// be stored indefinitely
TTL time.Duration
}
// WriteOption sets attributes on WriteOptions
// WriteOption sets attributes on WriteOptions.
type WriteOption func(o *WriteOptions)
// WithTTL sets the TTL attribute on WriteOptions
// WithTTL sets the TTL attribute on WriteOptions.
func WithTTL(d time.Duration) WriteOption {
return func(o *WriteOptions) {
o.TTL = d
}
}
// ReadOptions contains all the options which can be provided when reading events from a store
// ReadOptions contains all the options which can be provided when reading events from a store.
type ReadOptions struct {
// Limit the number of results to return
Limit uint
@@ -152,17 +152,17 @@ type ReadOptions struct {
Offset uint
}
// ReadOption sets attributes on ReadOptions
// ReadOption sets attributes on ReadOptions.
type ReadOption func(o *ReadOptions)
// ReadLimit sets the limit attribute on ReadOptions
// ReadLimit sets the limit attribute on ReadOptions.
func ReadLimit(l uint) ReadOption {
return func(o *ReadOptions) {
o.Limit = 1
}
}
// ReadOffset sets the offset attribute on ReadOptions
// ReadOffset sets the offset attribute on ReadOptions.
func ReadOffset(l uint) ReadOption {
return func(o *ReadOptions) {
o.Offset = 1
+4 -5
View File
@@ -5,14 +5,13 @@ import (
"time"
"github.com/pkg/errors"
log "go-micro.dev/v4/logger"
"go-micro.dev/v4/store"
)
const joinKey = "/"
// NewStore returns an initialized events store
// NewStore returns an initialized events store.
func NewStore(opts ...StoreOption) Store {
// parse the options
var options StoreOptions
@@ -41,7 +40,7 @@ type evStore struct {
store store.Store
}
// Read events for a topic
// Read events for a topic.
func (s *evStore) Read(topic string, opts ...ReadOption) ([]*Event, error) {
// validate the topic
if len(topic) == 0 {
@@ -80,7 +79,7 @@ func (s *evStore) Read(topic string, opts ...ReadOption) ([]*Event, error) {
return result, nil
}
// Write an event to the store
// Write an event to the store.
func (s *evStore) Write(event *Event, opts ...WriteOption) error {
// parse the options
options := WriteOptions{
@@ -124,7 +123,7 @@ func (s *evStore) backupLoop() {
}
}
// Backup is an interface for snapshotting the events store to long term storage
// Backup is an interface for snapshotting the events store to long term storage.
type Backup interface {
Snapshot(st store.Store) error
}
+14 -4
View File
@@ -23,15 +23,16 @@ func init() {
}
type defaultLogger struct {
sync.RWMutex
opts Options
sync.RWMutex
}
// Init (opts...) should only overwrite provided options
// Init (opts...) should only overwrite provided options.
func (l *defaultLogger) Init(opts ...Option) error {
for _, o := range opts {
o(&l.opts)
}
return nil
}
@@ -42,6 +43,7 @@ func (l *defaultLogger) String() string {
func (l *defaultLogger) Fields(fields map[string]interface{}) Logger {
l.Lock()
nfields := make(map[string]interface{}, len(l.opts.Fields))
for k, v := range l.opts.Fields {
nfields[k] = v
}
@@ -65,6 +67,7 @@ func copyFields(src map[string]interface{}) map[string]interface{} {
for k, v := range src {
dst[k] = v
}
return dst
}
@@ -85,10 +88,13 @@ func logCallerfilePath(loggingFilePath string) string {
if idx == -1 {
return loggingFilePath
}
idx = strings.LastIndexByte(loggingFilePath[:idx], '/')
if idx == -1 {
return loggingFilePath
}
return loggingFilePath[idx+1:]
}
@@ -121,6 +127,7 @@ func (l *defaultLogger) Log(level Level, v ...interface{}) {
}
sort.Strings(keys)
metadata := ""
for _, k := range keys {
@@ -162,6 +169,7 @@ func (l *defaultLogger) Logf(level Level, format string, v ...interface{}) {
}
sort.Strings(keys)
metadata := ""
for _, k := range keys {
@@ -177,13 +185,15 @@ func (l *defaultLogger) Logf(level Level, format string, v ...interface{}) {
func (l *defaultLogger) Options() Options {
// not guard against options Context values
l.RLock()
defer l.RUnlock()
opts := l.opts
opts.Fields = copyFields(l.opts.Fields)
l.RUnlock()
return opts
}
// NewLogger builds a new logger based on options
// NewLogger builds a new logger based on options.
func NewLogger(opts ...Option) Logger {
// Default options
options := Options{
+1 -1
View File
@@ -116,7 +116,7 @@ func Fatalf(template string, args ...interface{}) {
os.Exit(1)
}
// Returns true if the given level is at or lower the current logger level
// Returns true if the given level is at or lower the current logger level.
func V(lvl Level, log Logger) bool {
l := DefaultLogger
if log != nil {
+4 -4
View File
@@ -2,16 +2,16 @@
package logger
var (
// Default logger
// Default logger.
DefaultLogger Logger = NewLogger()
// Default logger helper
// Default logger helper.
DefaultHelper *Helper = NewHelper(DefaultLogger)
)
// Logger is a generic logging interface
// Logger is a generic logging interface.
type Logger interface {
// Init initialises options
// Init initializes options
Init(options ...Option) error
// The Logger options
Options() Options
+10 -10
View File
@@ -8,40 +8,40 @@ import (
type Option func(*Options)
type Options struct {
// The logging level the logger should log at. default is `InfoLevel`
Level Level
// fields to always be logged
Fields map[string]interface{}
// It's common to set this to a file, or leave it default which is `os.Stderr`
Out io.Writer
// Caller skip frame count for file:line info
CallerSkipCount int
// Alternative options
Context context.Context
// fields to always be logged
Fields map[string]interface{}
// Caller skip frame count for file:line info
CallerSkipCount int
// The logging level the logger should log at. default is `InfoLevel`
Level Level
}
// WithFields set default fields for the logger
// WithFields set default fields for the logger.
func WithFields(fields map[string]interface{}) Option {
return func(args *Options) {
args.Fields = fields
}
}
// WithLevel set default level for the logger
// WithLevel set default level for the logger.
func WithLevel(level Level) Option {
return func(args *Options) {
args.Level = level
}
}
// WithOutput set default output writer for the logger
// WithOutput set default output writer for the logger.
func WithOutput(out io.Writer) Option {
return func(args *Options) {
args.Out = out
}
}
// WithCallerSkipCount set frame count to skip
// WithCallerSkipCount set frame count to skip.
func WithCallerSkipCount(c int) Option {
return func(args *Options) {
args.CallerSkipCount = c
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

+7 -7
View File
@@ -36,7 +36,7 @@ func (md Metadata) Delete(key string) {
delete(md, strings.Title(key))
}
// Copy makes a copy of the metadata
// Copy makes a copy of the metadata.
func Copy(md Metadata) Metadata {
cmd := make(Metadata, len(md))
for k, v := range md {
@@ -45,12 +45,12 @@ func Copy(md Metadata) Metadata {
return cmd
}
// Delete key from metadata
// Delete key from metadata.
func Delete(ctx context.Context, k string) context.Context {
return Set(ctx, k, "")
}
// Set add key with val to metadata
// Set add key with val to metadata.
func Set(ctx context.Context, k, v string) context.Context {
md, ok := FromContext(ctx)
if !ok {
@@ -64,7 +64,7 @@ func Set(ctx context.Context, k, v string) context.Context {
return context.WithValue(ctx, metadataKey{}, md)
}
// Get returns a single value from metadata in the context
// Get returns a single value from metadata in the context.
func Get(ctx context.Context, key string) (string, bool) {
md, ok := FromContext(ctx)
if !ok {
@@ -82,7 +82,7 @@ func Get(ctx context.Context, key string) (string, bool) {
return val, ok
}
// FromContext returns metadata from the given context
// FromContext returns metadata from the given context.
func FromContext(ctx context.Context) (Metadata, bool) {
md, ok := ctx.Value(metadataKey{}).(Metadata)
if !ok {
@@ -98,12 +98,12 @@ func FromContext(ctx context.Context) (Metadata, bool) {
return newMD, ok
}
// NewContext creates a new context with the given metadata
// NewContext creates a new context with the given metadata.
func NewContext(ctx context.Context, md Metadata) context.Context {
return context.WithValue(ctx, metadataKey{}, md)
}
// MergeContext merges metadata to existing metadata, overwriting if specified
// MergeContext merges metadata to existing metadata, overwriting if specified.
func MergeContext(ctx context.Context, patchMd Metadata, overwrite bool) context.Context {
if ctx == nil {
ctx = context.Background()
+8 -7
View File
@@ -12,11 +12,11 @@ type serviceKey struct{}
// Service is an interface that wraps the lower level libraries
// within go-micro. Its a convenience method for building
// and initialising services.
// and initializing services.
type Service interface {
// The service name
Name() string
// Init initialises options
// Init initializes options
Init(...Option)
// Options returns the current options
Options() Options
@@ -30,13 +30,13 @@ type Service interface {
String() string
}
// Event is used to publish messages to a topic
// Event is used to publish messages to a topic.
type Event interface {
// Publish publishes a message to the event topic
Publish(ctx context.Context, msg interface{}, opts ...client.PublishOption) error
}
// Type alias to satisfy the deprecation
// Type alias to satisfy the deprecation.
type Publisher = Event
type Option func(*Options)
@@ -57,20 +57,21 @@ func NewContext(ctx context.Context, s Service) context.Context {
return context.WithValue(ctx, serviceKey{}, s)
}
// NewEvent creates a new event publisher
// NewEvent creates a new event publisher.
func NewEvent(topic string, c client.Client) Event {
if c == nil {
c = client.NewClient()
}
return &event{c, topic}
}
// RegisterHandler is syntactic sugar for registering a handler
// RegisterHandler is syntactic sugar for registering a handler.
func RegisterHandler(s server.Server, h interface{}, opts ...server.HandlerOption) error {
return s.Handle(s.NewHandler(h, opts...))
}
// RegisterSubscriber is syntactic sugar for registering a subscriber
// RegisterSubscriber is syntactic sugar for registering a subscriber.
func RegisterSubscriber(topic string, s server.Server, h interface{}, opts ...server.SubscriberOption) error {
return s.Subscribe(s.NewSubscriber(topic, h, opts...))
}
+59 -48
View File
@@ -22,31 +22,33 @@ import (
"go-micro.dev/v4/util/cmd"
)
// Options for micro service
// Options for micro service.
type Options struct {
Auth auth.Auth
Broker broker.Broker
Cache cache.Cache
Cmd cmd.Cmd
Config config.Config
Client client.Client
Server server.Server
Store store.Store
Registry registry.Registry
Runtime runtime.Runtime
Transport transport.Transport
Profile profile.Profile
Logger logger.Logger
// Before and After funcs
BeforeStart []func() error
BeforeStop []func() error
AfterStart []func() error
AfterStop []func() error
Registry registry.Registry
Store store.Store
Auth auth.Auth
Cmd cmd.Cmd
Config config.Config
Client client.Client
Server server.Server
// Other options for implementations of the interface
// can be stored in a context
Context context.Context
Cache cache.Cache
Runtime runtime.Runtime
Profile profile.Profile
Transport transport.Transport
Logger logger.Logger
Broker broker.Broker
// Before and After funcs
BeforeStart []func() error
AfterStart []func() error
AfterStop []func() error
BeforeStop []func() error
Signal bool
}
@@ -75,7 +77,7 @@ func newOptions(opts ...Option) Options {
return opt
}
// Broker to be used for service
// Broker to be used for service.
func Broker(b broker.Broker) Option {
return func(o *Options) {
o.Broker = b
@@ -97,7 +99,7 @@ func Cmd(c cmd.Cmd) Option {
}
}
// Client to be used for service
// Client to be used for service.
func Client(c client.Client) Option {
return func(o *Options) {
o.Client = c
@@ -112,6 +114,15 @@ func Context(ctx context.Context) Option {
}
}
// Handle will register a handler without any fuss
func Handle(v interface{}) Option {
return func(o *Options) {
o.Server.Handle(
o.Server.NewHandler(v),
)
}
}
// HandleSignal toggles automatic installation of the signal handler that
// traps TERM, INT, and QUIT. Users of this feature to disable the signal
// handler, should control liveness of the service through the context.
@@ -121,21 +132,21 @@ func HandleSignal(b bool) Option {
}
}
// Profile to be used for debug profile
// Profile to be used for debug profile.
func Profile(p profile.Profile) Option {
return func(o *Options) {
o.Profile = p
}
}
// Server to be used for service
// Server to be used for service.
func Server(s server.Server) Option {
return func(o *Options) {
o.Server = s
}
}
// Store sets the store to use
// Store sets the store to use.
func Store(s store.Store) Option {
return func(o *Options) {
o.Store = s
@@ -143,7 +154,7 @@ func Store(s store.Store) Option {
}
// Registry sets the registry for the service
// and the underlying components
// and the underlying components.
func Registry(r registry.Registry) Option {
return func(o *Options) {
o.Registry = r
@@ -155,28 +166,28 @@ func Registry(r registry.Registry) Option {
}
}
// Tracer sets the tracer for the service
// Tracer sets the tracer for the service.
func Tracer(t trace.Tracer) Option {
return func(o *Options) {
o.Server.Init(server.Tracer(t))
}
}
// Auth sets the auth for the service
// Auth sets the auth for the service.
func Auth(a auth.Auth) Option {
return func(o *Options) {
o.Auth = a
}
}
// Config sets the config for the service
// Config sets the config for the service.
func Config(c config.Config) Option {
return func(o *Options) {
o.Config = c
}
}
// Selector sets the selector for the service client
// Selector sets the selector for the service client.
func Selector(s selector.Selector) Option {
return func(o *Options) {
o.Client.Init(client.Selector(s))
@@ -184,7 +195,7 @@ func Selector(s selector.Selector) Option {
}
// Transport sets the transport for the service
// and the underlying components
// and the underlying components.
func Transport(t transport.Transport) Option {
return func(o *Options) {
o.Transport = t
@@ -194,7 +205,7 @@ func Transport(t transport.Transport) Option {
}
}
// Runtime sets the runtime
// Runtime sets the runtime.
func Runtime(r runtime.Runtime) Option {
return func(o *Options) {
o.Runtime = r
@@ -203,56 +214,56 @@ func Runtime(r runtime.Runtime) Option {
// Convenience options
// Address sets the address of the server
// Address sets the address of the server.
func Address(addr string) Option {
return func(o *Options) {
o.Server.Init(server.Address(addr))
}
}
// Name of the service
// Name of the service.
func Name(n string) Option {
return func(o *Options) {
o.Server.Init(server.Name(n))
}
}
// Version of the service
// Version of the service.
func Version(v string) Option {
return func(o *Options) {
o.Server.Init(server.Version(v))
}
}
// Metadata associated with the service
// Metadata associated with the service.
func Metadata(md map[string]string) Option {
return func(o *Options) {
o.Server.Init(server.Metadata(md))
}
}
// Flags that can be passed to service
// Flags that can be passed to service.
func Flags(flags ...cli.Flag) Option {
return func(o *Options) {
o.Cmd.App().Flags = append(o.Cmd.App().Flags, flags...)
}
}
// Action can be used to parse user provided cli options
// Action can be used to parse user provided cli options.
func Action(a func(*cli.Context) error) Option {
return func(o *Options) {
o.Cmd.App().Action = a
}
}
// RegisterTTL specifies the TTL to use when registering the service
// RegisterTTL specifies the TTL to use when registering the service.
func RegisterTTL(t time.Duration) Option {
return func(o *Options) {
o.Server.Init(server.RegisterTTL(t))
}
}
// RegisterInterval specifies the interval on which to re-register
// RegisterInterval specifies the interval on which to re-register.
func RegisterInterval(t time.Duration) Option {
return func(o *Options) {
o.Server.Init(server.RegisterInterval(t))
@@ -271,14 +282,14 @@ func WrapClient(w ...client.Wrapper) Option {
}
}
// WrapCall is a convenience method for wrapping a Client CallFunc
// WrapCall is a convenience method for wrapping a Client CallFunc.
func WrapCall(w ...client.CallWrapper) Option {
return func(o *Options) {
o.Client.Init(client.WrapCall(w...))
}
}
// WrapHandler adds a handler Wrapper to a list of options passed into the server
// WrapHandler adds a handler Wrapper to a list of options passed into the server.
func WrapHandler(w ...server.HandlerWrapper) Option {
return func(o *Options) {
var wrappers []server.Option
@@ -292,7 +303,7 @@ func WrapHandler(w ...server.HandlerWrapper) Option {
}
}
// WrapSubscriber adds a subscriber Wrapper to a list of options passed into the server
// WrapSubscriber adds a subscriber Wrapper to a list of options passed into the server.
func WrapSubscriber(w ...server.SubscriberWrapper) Option {
return func(o *Options) {
var wrappers []server.Option
@@ -306,7 +317,7 @@ func WrapSubscriber(w ...server.SubscriberWrapper) Option {
}
}
// Add opt to server option
// Add opt to server option.
func AddListenOption(option server.Option) Option {
return func(o *Options) {
o.Server.Init(option)
@@ -315,35 +326,35 @@ func AddListenOption(option server.Option) Option {
// Before and Afters
// BeforeStart run funcs before service starts
// BeforeStart run funcs before service starts.
func BeforeStart(fn func() error) Option {
return func(o *Options) {
o.BeforeStart = append(o.BeforeStart, fn)
}
}
// BeforeStop run funcs before service stops
// BeforeStop run funcs before service stops.
func BeforeStop(fn func() error) Option {
return func(o *Options) {
o.BeforeStop = append(o.BeforeStop, fn)
}
}
// AfterStart run funcs after service starts
// AfterStart run funcs after service starts.
func AfterStart(fn func() error) Option {
return func(o *Options) {
o.AfterStart = append(o.AfterStart, fn)
}
}
// AfterStop run funcs after service stops
// AfterStop run funcs after service stops.
func AfterStop(fn func() error) Option {
return func(o *Options) {
o.AfterStop = append(o.AfterStop, fn)
}
}
// Logger sets the logger for the service
// Logger sets the logger for the service.
func Logger(l logger.Logger) Option {
return func(o *Options) {
o.Logger = l

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