v3 refactor (#1868)

* Move to v3

Co-authored-by: Ben Toogood <bentoogood@gmail.com>
This commit is contained in:
Asim Aslam 2020-07-27 13:22:00 +01:00 committed by GitHub
parent 9dfeb98111
commit 563768b58a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
424 changed files with 6383 additions and 22490 deletions

View File

@ -1,37 +0,0 @@
name: Build all github.com/micro/examples
on:
pull_request:
branches:
- master
jobs:
build:
name: Build repos
runs-on: ubuntu-latest
steps:
- name: Set up Go 1.13
uses: actions/setup-go@v1
with:
go-version: 1.13
id: go
- name: Install Protoc
uses: arduino/setup-protoc@master
- name: Check out this code
uses: actions/checkout@v2
with:
path: 'go-micro'
- name: Check out code examples
uses: actions/checkout@v2
with:
repository: 'micro/examples'
path: 'examples'
- name: Build all
run: $GITHUB_WORKSPACE/go-micro/.github/workflows/scripts/build-all-examples.sh ${{ github.event.pull_request.head.sha }} ${{ github.event.pull_request.head.repo.full_name }}
working-directory: examples

View File

@ -1,37 +0,0 @@
name: Build and test micro
on:
pull_request:
branches:
- master
jobs:
build:
name: Build and test micro
runs-on: ubuntu-latest
steps:
- name: Set up Go 1.13
uses: actions/setup-go@v2
with:
go-version: 1.13
id: go
- name: Install Protoc
uses: arduino/setup-protoc@master
- name: Check out this code
uses: actions/checkout@v2
with:
path: 'go-micro'
- name: Check out micro
uses: actions/checkout@v2
with:
repository: 'micro/micro'
path: 'micro'
- name: Build all
run: $GITHUB_WORKSPACE/go-micro/.github/workflows/scripts/build-micro.sh ${{ github.event.pull_request.head.sha }} ${{ github.event.pull_request.head.repo.full_name }}
working-directory: micro

View File

@ -6,8 +6,8 @@ import (
"sync" "sync"
"github.com/bwmarrin/discordgo" "github.com/bwmarrin/discordgo"
"github.com/micro/go-micro/v2/agent/input" "github.com/micro/go-micro/v3/agent/input"
"github.com/micro/go-micro/v2/logger" "github.com/micro/go-micro/v3/logger"
) )
type discordConn struct { type discordConn struct {

View File

@ -9,7 +9,7 @@ import (
"github.com/bwmarrin/discordgo" "github.com/bwmarrin/discordgo"
"github.com/micro/cli/v2" "github.com/micro/cli/v2"
"github.com/micro/go-micro/v2/agent/input" "github.com/micro/go-micro/v3/agent/input"
) )
func init() { func init() {

View File

@ -7,7 +7,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/micro/go-micro/v2/agent/input" "github.com/micro/go-micro/v3/agent/input"
"github.com/nlopes/slack" "github.com/nlopes/slack"
) )

View File

@ -5,7 +5,7 @@ import (
"sync" "sync"
"github.com/micro/cli/v2" "github.com/micro/cli/v2"
"github.com/micro/go-micro/v2/agent/input" "github.com/micro/go-micro/v3/agent/input"
"github.com/nlopes/slack" "github.com/nlopes/slack"
) )

View File

@ -6,8 +6,8 @@ import (
"sync" "sync"
"github.com/forestgiant/sliceutil" "github.com/forestgiant/sliceutil"
"github.com/micro/go-micro/v2/agent/input" "github.com/micro/go-micro/v3/agent/input"
"github.com/micro/go-micro/v2/logger" "github.com/micro/go-micro/v3/logger"
tgbotapi "gopkg.in/telegram-bot-api.v4" tgbotapi "gopkg.in/telegram-bot-api.v4"
) )

View File

@ -6,7 +6,7 @@ import (
"sync" "sync"
"github.com/micro/cli/v2" "github.com/micro/cli/v2"
"github.com/micro/go-micro/v2/agent/input" "github.com/micro/go-micro/v3/agent/input"
tgbotapi "gopkg.in/telegram-bot-api.v4" tgbotapi "gopkg.in/telegram-bot-api.v4"
) )

View File

@ -11,9 +11,9 @@ import (
import ( import (
context "context" context "context"
api "github.com/micro/go-micro/v2/api" api "github.com/micro/go-micro/v3/api"
client "github.com/micro/go-micro/v2/client" client "github.com/micro/go-micro/v3/client"
server "github.com/micro/go-micro/v2/server" server "github.com/micro/go-micro/v3/server"
) )
// Reference imports to suppress errors if they are not otherwise used. // Reference imports to suppress errors if they are not otherwise used.

View File

@ -5,8 +5,8 @@ import (
"regexp" "regexp"
"strings" "strings"
"github.com/micro/go-micro/v2/registry" "github.com/micro/go-micro/v3/registry"
"github.com/micro/go-micro/v2/server" "github.com/micro/go-micro/v3/server"
) )
type Api interface { type Api interface {

View File

@ -4,13 +4,13 @@ package api
import ( import (
"net/http" "net/http"
goapi "github.com/micro/go-micro/v2/api" goapi "github.com/micro/go-micro/v3/api"
"github.com/micro/go-micro/v2/api/handler" "github.com/micro/go-micro/v3/api/handler"
"github.com/micro/go-micro/v2/api/handler/util" "github.com/micro/go-micro/v3/api/handler/util"
api "github.com/micro/go-micro/v2/api/proto" api "github.com/micro/go-micro/v3/api/proto"
"github.com/micro/go-micro/v2/client" "github.com/micro/go-micro/v3/client"
"github.com/micro/go-micro/v2/errors" "github.com/micro/go-micro/v3/errors"
"github.com/micro/go-micro/v2/util/ctx" "github.com/micro/go-micro/v3/util/ctx"
) )
type apiHandler struct { type apiHandler struct {

View File

@ -7,7 +7,7 @@ import (
"net/http" "net/http"
"strings" "strings"
api "github.com/micro/go-micro/v2/api/proto" api "github.com/micro/go-micro/v3/api/proto"
"github.com/oxtoacart/bpool" "github.com/oxtoacart/bpool"
) )

View File

@ -11,9 +11,9 @@ import (
"time" "time"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/micro/go-micro/v2/api/handler" "github.com/micro/go-micro/v3/api/handler"
proto "github.com/micro/go-micro/v2/api/proto" proto "github.com/micro/go-micro/v3/api/proto"
"github.com/micro/go-micro/v2/util/ctx" "github.com/micro/go-micro/v3/util/ctx"
"github.com/oxtoacart/bpool" "github.com/oxtoacart/bpool"
) )

View File

@ -9,9 +9,9 @@ import (
"net/http/httputil" "net/http/httputil"
"net/url" "net/url"
"github.com/micro/go-micro/v2/api" "github.com/micro/go-micro/v3/api"
"github.com/micro/go-micro/v2/api/handler" "github.com/micro/go-micro/v3/api/handler"
"github.com/micro/go-micro/v2/registry" "github.com/micro/go-micro/v3/registry"
) )
const ( const (

View File

@ -6,13 +6,13 @@ import (
"net/http/httptest" "net/http/httptest"
"testing" "testing"
"github.com/micro/go-micro/v2/api/handler" "github.com/micro/go-micro/v3/api/handler"
"github.com/micro/go-micro/v2/api/resolver" "github.com/micro/go-micro/v3/api/resolver"
"github.com/micro/go-micro/v2/api/resolver/vpath" "github.com/micro/go-micro/v3/api/resolver/vpath"
"github.com/micro/go-micro/v2/api/router" "github.com/micro/go-micro/v3/api/router"
regRouter "github.com/micro/go-micro/v2/api/router/registry" regRouter "github.com/micro/go-micro/v3/api/router/registry"
"github.com/micro/go-micro/v2/registry" "github.com/micro/go-micro/v3/registry"
"github.com/micro/go-micro/v2/registry/memory" "github.com/micro/go-micro/v3/registry/memory"
) )
func testHttp(t *testing.T, path, service, ns string) { func testHttp(t *testing.T, path, service, ns string) {

View File

@ -1,9 +1,9 @@
package handler package handler
import ( import (
"github.com/micro/go-micro/v2/api/router" "github.com/micro/go-micro/v3/api/router"
"github.com/micro/go-micro/v2/client" "github.com/micro/go-micro/v3/client"
"github.com/micro/go-micro/v2/client/grpc" "github.com/micro/go-micro/v3/client/grpc"
) )
var ( var (

View File

@ -9,19 +9,19 @@ import (
"strings" "strings"
jsonpatch "github.com/evanphx/json-patch/v5" jsonpatch "github.com/evanphx/json-patch/v5"
"github.com/micro/go-micro/v2/api" "github.com/micro/go-micro/v3/api"
"github.com/micro/go-micro/v2/api/handler" "github.com/micro/go-micro/v3/api/handler"
"github.com/micro/go-micro/v2/api/handler/util" "github.com/micro/go-micro/v3/api/handler/util"
"github.com/micro/go-micro/v2/api/internal/proto" "github.com/micro/go-micro/v3/api/internal/proto"
"github.com/micro/go-micro/v2/client" "github.com/micro/go-micro/v3/client"
"github.com/micro/go-micro/v2/codec" "github.com/micro/go-micro/v3/codec"
"github.com/micro/go-micro/v2/codec/jsonrpc" "github.com/micro/go-micro/v3/codec/jsonrpc"
"github.com/micro/go-micro/v2/codec/protorpc" "github.com/micro/go-micro/v3/codec/protorpc"
"github.com/micro/go-micro/v2/errors" "github.com/micro/go-micro/v3/errors"
"github.com/micro/go-micro/v2/logger" "github.com/micro/go-micro/v3/logger"
"github.com/micro/go-micro/v2/metadata" "github.com/micro/go-micro/v3/metadata"
"github.com/micro/go-micro/v2/util/ctx" "github.com/micro/go-micro/v3/util/ctx"
"github.com/micro/go-micro/v2/util/qson" "github.com/micro/go-micro/v3/util/qson"
"github.com/oxtoacart/bpool" "github.com/oxtoacart/bpool"
) )

View File

@ -7,7 +7,7 @@ import (
"testing" "testing"
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
go_api "github.com/micro/go-micro/v2/api/proto" go_api "github.com/micro/go-micro/v3/api/proto"
) )
func TestRequestPayloadFromRequest(t *testing.T) { func TestRequestPayloadFromRequest(t *testing.T) {

View File

@ -12,11 +12,11 @@ import (
"github.com/gobwas/httphead" "github.com/gobwas/httphead"
"github.com/gobwas/ws" "github.com/gobwas/ws"
"github.com/gobwas/ws/wsutil" "github.com/gobwas/ws/wsutil"
"github.com/micro/go-micro/v2/api" "github.com/micro/go-micro/v3/api"
"github.com/micro/go-micro/v2/api/handler/util" "github.com/micro/go-micro/v3/api/handler/util"
"github.com/micro/go-micro/v2/client" "github.com/micro/go-micro/v3/client"
raw "github.com/micro/go-micro/v2/codec/bytes" raw "github.com/micro/go-micro/v3/codec/bytes"
"github.com/micro/go-micro/v2/logger" "github.com/micro/go-micro/v3/logger"
) )
// serveWebsocket will stream rpc back over websockets assuming json // serveWebsocket will stream rpc back over websockets assuming json

View File

@ -1,8 +1,8 @@
package util package util
import ( import (
"github.com/micro/go-micro/v2/registry" "github.com/micro/go-micro/v3/registry"
"github.com/micro/go-micro/v2/router" "github.com/micro/go-micro/v3/router"
) )
// Router is a hack for API routing // Router is a hack for API routing

View File

@ -12,9 +12,9 @@ import (
"net/url" "net/url"
"strings" "strings"
"github.com/micro/go-micro/v2/api" "github.com/micro/go-micro/v3/api"
"github.com/micro/go-micro/v2/api/handler" "github.com/micro/go-micro/v3/api/handler"
"github.com/micro/go-micro/v2/registry" "github.com/micro/go-micro/v3/registry"
) )
const ( const (

View File

@ -6,7 +6,7 @@ import (
"net/http" "net/http"
"strings" "strings"
"github.com/micro/go-micro/v2/api/resolver" "github.com/micro/go-micro/v3/api/resolver"
) )
type Resolver struct { type Resolver struct {

View File

@ -4,7 +4,7 @@ package host
import ( import (
"net/http" "net/http"
"github.com/micro/go-micro/v2/api/resolver" "github.com/micro/go-micro/v3/api/resolver"
) )
type Resolver struct { type Resolver struct {

View File

@ -1,7 +1,7 @@
package resolver package resolver
import ( import (
"github.com/micro/go-micro/v2/registry" "github.com/micro/go-micro/v3/registry"
) )
type Options struct { type Options struct {

View File

@ -5,7 +5,7 @@ import (
"net/http" "net/http"
"strings" "strings"
"github.com/micro/go-micro/v2/api/resolver" "github.com/micro/go-micro/v3/api/resolver"
) )
type Resolver struct { type Resolver struct {

View File

@ -7,8 +7,8 @@ import (
"net/http" "net/http"
"strings" "strings"
"github.com/micro/go-micro/v2/api/resolver" "github.com/micro/go-micro/v3/api/resolver"
"github.com/micro/go-micro/v2/logger" "github.com/micro/go-micro/v3/logger"
"golang.org/x/net/publicsuffix" "golang.org/x/net/publicsuffix"
) )

View File

@ -5,7 +5,7 @@ import (
"net/url" "net/url"
"testing" "testing"
"github.com/micro/go-micro/v2/api/resolver/vpath" "github.com/micro/go-micro/v3/api/resolver/vpath"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )

View File

@ -7,7 +7,7 @@ import (
"regexp" "regexp"
"strings" "strings"
"github.com/micro/go-micro/v2/api/resolver" "github.com/micro/go-micro/v3/api/resolver"
) )
func NewResolver(opts ...resolver.Option) resolver.Resolver { func NewResolver(opts ...resolver.Option) resolver.Resolver {

View File

@ -1,9 +1,10 @@
package router package router
import ( import (
"github.com/micro/go-micro/v2/api/resolver" "github.com/micro/go-micro/v3/api/resolver"
"github.com/micro/go-micro/v2/api/resolver/vpath" "github.com/micro/go-micro/v3/api/resolver/vpath"
"github.com/micro/go-micro/v2/registry" "github.com/micro/go-micro/v3/registry"
"github.com/micro/go-micro/v3/registry/mdns"
) )
type Options struct { type Options struct {
@ -17,7 +18,7 @@ type Option func(o *Options)
func NewOptions(opts ...Option) Options { func NewOptions(opts ...Option) Options {
options := Options{ options := Options{
Handler: "meta", Handler: "meta",
Registry: registry.DefaultRegistry, Registry: mdns.NewRegistry(),
} }
for _, o := range opts { for _, o := range opts {

View File

@ -10,13 +10,13 @@ import (
"sync" "sync"
"time" "time"
"github.com/micro/go-micro/v2/api" "github.com/micro/go-micro/v3/api"
"github.com/micro/go-micro/v2/api/router" "github.com/micro/go-micro/v3/api/router"
"github.com/micro/go-micro/v2/api/router/util" "github.com/micro/go-micro/v3/api/router/util"
"github.com/micro/go-micro/v2/logger" "github.com/micro/go-micro/v3/logger"
"github.com/micro/go-micro/v2/metadata" "github.com/micro/go-micro/v3/metadata"
"github.com/micro/go-micro/v2/registry" "github.com/micro/go-micro/v3/registry"
"github.com/micro/go-micro/v2/registry/cache" "github.com/micro/go-micro/v3/registry/cache"
) )
// endpoint struct, that holds compiled pcre // endpoint struct, that holds compiled pcre

View File

@ -3,7 +3,7 @@ package registry
import ( import (
"testing" "testing"
"github.com/micro/go-micro/v2/registry" "github.com/micro/go-micro/v3/registry"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )

View File

@ -4,7 +4,7 @@ package router
import ( import (
"net/http" "net/http"
"github.com/micro/go-micro/v2/api" "github.com/micro/go-micro/v3/api"
) )
// Router is used to determine an endpoint for a request // Router is used to determine an endpoint for a request

View File

@ -9,18 +9,18 @@ import (
"testing" "testing"
"time" "time"
"github.com/micro/go-micro/v2/api" "github.com/micro/go-micro/v3/api"
"github.com/micro/go-micro/v2/api/handler" "github.com/micro/go-micro/v3/api/handler"
"github.com/micro/go-micro/v2/api/handler/rpc" "github.com/micro/go-micro/v3/api/handler/rpc"
"github.com/micro/go-micro/v2/api/router" "github.com/micro/go-micro/v3/api/router"
rregistry "github.com/micro/go-micro/v2/api/router/registry" rregistry "github.com/micro/go-micro/v3/api/router/registry"
rstatic "github.com/micro/go-micro/v2/api/router/static" rstatic "github.com/micro/go-micro/v3/api/router/static"
"github.com/micro/go-micro/v2/client" "github.com/micro/go-micro/v3/client"
gcli "github.com/micro/go-micro/v2/client/grpc" gcli "github.com/micro/go-micro/v3/client/grpc"
rmemory "github.com/micro/go-micro/v2/registry/memory" rmemory "github.com/micro/go-micro/v3/registry/memory"
"github.com/micro/go-micro/v2/server" "github.com/micro/go-micro/v3/server"
gsrv "github.com/micro/go-micro/v2/server/grpc" gsrv "github.com/micro/go-micro/v3/server/grpc"
pb "github.com/micro/go-micro/v2/server/grpc/proto" pb "github.com/micro/go-micro/v3/server/grpc/proto"
) )
// server is used to implement helloworld.GreeterServer. // server is used to implement helloworld.GreeterServer.

View File

@ -8,13 +8,13 @@ import (
"strings" "strings"
"sync" "sync"
"github.com/micro/go-micro/v2/api" "github.com/micro/go-micro/v3/api"
"github.com/micro/go-micro/v2/api/router" "github.com/micro/go-micro/v3/api/router"
"github.com/micro/go-micro/v2/api/router/util" "github.com/micro/go-micro/v3/api/router/util"
"github.com/micro/go-micro/v2/logger" "github.com/micro/go-micro/v3/logger"
"github.com/micro/go-micro/v2/metadata" "github.com/micro/go-micro/v3/metadata"
"github.com/micro/go-micro/v2/registry" "github.com/micro/go-micro/v3/registry"
rutil "github.com/micro/go-micro/v2/util/registry" rutil "github.com/micro/go-micro/v3/util/registry"
) )
type endpoint struct { type endpoint struct {

View File

@ -6,7 +6,7 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/micro/go-micro/v2/logger" "github.com/micro/go-micro/v3/logger"
) )
// InvalidTemplateError indicates that the path template is not valid. // InvalidTemplateError indicates that the path template is not valid.

View File

@ -8,7 +8,7 @@ import (
"reflect" "reflect"
"testing" "testing"
"github.com/micro/go-micro/v2/logger" "github.com/micro/go-micro/v3/logger"
) )
func TestTokenize(t *testing.T) { func TestTokenize(t *testing.T) {

View File

@ -7,7 +7,7 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/micro/go-micro/v2/logger" "github.com/micro/go-micro/v3/logger"
) )
var ( var (

View File

@ -7,8 +7,8 @@ import (
"net" "net"
"os" "os"
"github.com/micro/go-micro/v2/api/server/acme" "github.com/micro/go-micro/v3/api/server/acme"
"github.com/micro/go-micro/v2/logger" "github.com/micro/go-micro/v3/logger"
"golang.org/x/crypto/acme/autocert" "golang.org/x/crypto/acme/autocert"
) )

View File

@ -8,8 +8,8 @@ import (
"time" "time"
"github.com/caddyserver/certmagic" "github.com/caddyserver/certmagic"
"github.com/micro/go-micro/v2/api/server/acme" "github.com/micro/go-micro/v3/api/server/acme"
"github.com/micro/go-micro/v2/logger" "github.com/micro/go-micro/v3/logger"
) )
type certmagicProvider struct { type certmagicProvider struct {

View File

@ -10,8 +10,8 @@ import (
"time" "time"
"github.com/caddyserver/certmagic" "github.com/caddyserver/certmagic"
"github.com/micro/go-micro/v2/store" "github.com/micro/go-micro/v3/store"
"github.com/micro/go-micro/v2/sync" "github.com/micro/go-micro/v3/sync"
) )
// File represents a "File" that will be stored in store.Store - the contents and last modified time // File represents a "File" that will be stored in store.Store - the contents and last modified time

View File

@ -9,9 +9,9 @@ import (
"sync" "sync"
"github.com/gorilla/handlers" "github.com/gorilla/handlers"
"github.com/micro/go-micro/v2/api/server" "github.com/micro/go-micro/v3/api/server"
"github.com/micro/go-micro/v2/api/server/cors" "github.com/micro/go-micro/v3/api/server/cors"
"github.com/micro/go-micro/v2/logger" "github.com/micro/go-micro/v3/logger"
) )
type httpServer struct { type httpServer struct {

View File

@ -4,8 +4,8 @@ import (
"crypto/tls" "crypto/tls"
"net/http" "net/http"
"github.com/micro/go-micro/v2/api/resolver" "github.com/micro/go-micro/v3/api/resolver"
"github.com/micro/go-micro/v2/api/server/acme" "github.com/micro/go-micro/v3/api/server/acme"
) )
type Option func(o *Options) type Option func(o *Options)

View File

@ -11,9 +11,9 @@ import (
import ( import (
context "context" context "context"
api "github.com/micro/go-micro/v2/api" api "github.com/micro/go-micro/v3/api"
client "github.com/micro/go-micro/v2/client" client "github.com/micro/go-micro/v3/client"
server "github.com/micro/go-micro/v2/server" server "github.com/micro/go-micro/v3/server"
) )
// Reference imports to suppress errors if they are not otherwise used. // Reference imports to suppress errors if they are not otherwise used.

View File

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

View File

@ -5,9 +5,9 @@ import (
"sync" "sync"
"time" "time"
"github.com/micro/go-micro/v2/auth" "github.com/micro/go-micro/v3/auth"
"github.com/micro/go-micro/v2/util/token" "github.com/micro/go-micro/v3/util/token"
"github.com/micro/go-micro/v2/util/token/jwt" "github.com/micro/go-micro/v3/util/token/jwt"
) )
// NewAuth returns a new instance of the Auth service // NewAuth returns a new instance of the Auth service

81
auth/noop/noop.go Normal file
View File

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

View File

@ -4,8 +4,7 @@ import (
"context" "context"
"time" "time"
"github.com/micro/go-micro/v2/client" "github.com/micro/go-micro/v3/store"
"github.com/micro/go-micro/v2/store"
) )
func NewOptions(opts ...Option) Options { func NewOptions(opts ...Option) Options {
@ -13,10 +12,6 @@ func NewOptions(opts ...Option) Options {
for _, o := range opts { for _, o := range opts {
o(&options) o(&options)
} }
if options.Client == nil {
options.Client = client.DefaultClient
}
return options return options
} }
@ -37,10 +32,10 @@ type Options struct {
LoginURL string LoginURL string
// Store to back auth // Store to back auth
Store store.Store Store store.Store
// Client to use for RPC
Client client.Client
// Addrs sets the addresses of auth // Addrs sets the addresses of auth
Addrs []string Addrs []string
// Context to store other options
Context context.Context
} }
type Option func(o *Options) type Option func(o *Options)
@ -102,13 +97,6 @@ func LoginURL(url string) Option {
} }
} }
// WithClient sets the client to use when making requests
func WithClient(c client.Client) Option {
return func(o *Options) {
o.Client = c
}
}
type GenerateOptions struct { type GenerateOptions struct {
// Metadata associated with the account // Metadata associated with the account
Metadata map[string]string Metadata map[string]string

File diff suppressed because it is too large Load Diff

View File

@ -1,296 +0,0 @@
// Code generated by protoc-gen-micro. DO NOT EDIT.
// source: auth/service/proto/auth.proto
package go_micro_auth
import (
fmt "fmt"
proto "github.com/golang/protobuf/proto"
math "math"
)
import (
context "context"
api "github.com/micro/go-micro/v2/api"
client "github.com/micro/go-micro/v2/client"
server "github.com/micro/go-micro/v2/server"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
// Reference imports to suppress errors if they are not otherwise used.
var _ api.Endpoint
var _ context.Context
var _ client.Option
var _ server.Option
// Api Endpoints for Auth service
func NewAuthEndpoints() []*api.Endpoint {
return []*api.Endpoint{}
}
// Client API for Auth service
type AuthService interface {
Generate(ctx context.Context, in *GenerateRequest, opts ...client.CallOption) (*GenerateResponse, error)
Inspect(ctx context.Context, in *InspectRequest, opts ...client.CallOption) (*InspectResponse, error)
Token(ctx context.Context, in *TokenRequest, opts ...client.CallOption) (*TokenResponse, error)
}
type authService struct {
c client.Client
name string
}
func NewAuthService(name string, c client.Client) AuthService {
return &authService{
c: c,
name: name,
}
}
func (c *authService) Generate(ctx context.Context, in *GenerateRequest, opts ...client.CallOption) (*GenerateResponse, error) {
req := c.c.NewRequest(c.name, "Auth.Generate", in)
out := new(GenerateResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *authService) Inspect(ctx context.Context, in *InspectRequest, opts ...client.CallOption) (*InspectResponse, error) {
req := c.c.NewRequest(c.name, "Auth.Inspect", in)
out := new(InspectResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *authService) Token(ctx context.Context, in *TokenRequest, opts ...client.CallOption) (*TokenResponse, error) {
req := c.c.NewRequest(c.name, "Auth.Token", in)
out := new(TokenResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// Server API for Auth service
type AuthHandler interface {
Generate(context.Context, *GenerateRequest, *GenerateResponse) error
Inspect(context.Context, *InspectRequest, *InspectResponse) error
Token(context.Context, *TokenRequest, *TokenResponse) error
}
func RegisterAuthHandler(s server.Server, hdlr AuthHandler, opts ...server.HandlerOption) error {
type auth interface {
Generate(ctx context.Context, in *GenerateRequest, out *GenerateResponse) error
Inspect(ctx context.Context, in *InspectRequest, out *InspectResponse) error
Token(ctx context.Context, in *TokenRequest, out *TokenResponse) error
}
type Auth struct {
auth
}
h := &authHandler{hdlr}
return s.Handle(s.NewHandler(&Auth{h}, opts...))
}
type authHandler struct {
AuthHandler
}
func (h *authHandler) Generate(ctx context.Context, in *GenerateRequest, out *GenerateResponse) error {
return h.AuthHandler.Generate(ctx, in, out)
}
func (h *authHandler) Inspect(ctx context.Context, in *InspectRequest, out *InspectResponse) error {
return h.AuthHandler.Inspect(ctx, in, out)
}
func (h *authHandler) Token(ctx context.Context, in *TokenRequest, out *TokenResponse) error {
return h.AuthHandler.Token(ctx, in, out)
}
// Api Endpoints for Accounts service
func NewAccountsEndpoints() []*api.Endpoint {
return []*api.Endpoint{}
}
// Client API for Accounts service
type AccountsService interface {
List(ctx context.Context, in *ListAccountsRequest, opts ...client.CallOption) (*ListAccountsResponse, error)
Delete(ctx context.Context, in *DeleteAccountRequest, opts ...client.CallOption) (*DeleteAccountResponse, error)
}
type accountsService struct {
c client.Client
name string
}
func NewAccountsService(name string, c client.Client) AccountsService {
return &accountsService{
c: c,
name: name,
}
}
func (c *accountsService) List(ctx context.Context, in *ListAccountsRequest, opts ...client.CallOption) (*ListAccountsResponse, error) {
req := c.c.NewRequest(c.name, "Accounts.List", in)
out := new(ListAccountsResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *accountsService) Delete(ctx context.Context, in *DeleteAccountRequest, opts ...client.CallOption) (*DeleteAccountResponse, error) {
req := c.c.NewRequest(c.name, "Accounts.Delete", in)
out := new(DeleteAccountResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// Server API for Accounts service
type AccountsHandler interface {
List(context.Context, *ListAccountsRequest, *ListAccountsResponse) error
Delete(context.Context, *DeleteAccountRequest, *DeleteAccountResponse) error
}
func RegisterAccountsHandler(s server.Server, hdlr AccountsHandler, opts ...server.HandlerOption) error {
type accounts interface {
List(ctx context.Context, in *ListAccountsRequest, out *ListAccountsResponse) error
Delete(ctx context.Context, in *DeleteAccountRequest, out *DeleteAccountResponse) error
}
type Accounts struct {
accounts
}
h := &accountsHandler{hdlr}
return s.Handle(s.NewHandler(&Accounts{h}, opts...))
}
type accountsHandler struct {
AccountsHandler
}
func (h *accountsHandler) List(ctx context.Context, in *ListAccountsRequest, out *ListAccountsResponse) error {
return h.AccountsHandler.List(ctx, in, out)
}
func (h *accountsHandler) Delete(ctx context.Context, in *DeleteAccountRequest, out *DeleteAccountResponse) error {
return h.AccountsHandler.Delete(ctx, in, out)
}
// Api Endpoints for Rules service
func NewRulesEndpoints() []*api.Endpoint {
return []*api.Endpoint{}
}
// Client API for Rules service
type RulesService interface {
Create(ctx context.Context, in *CreateRequest, opts ...client.CallOption) (*CreateResponse, error)
Delete(ctx context.Context, in *DeleteRequest, opts ...client.CallOption) (*DeleteResponse, error)
List(ctx context.Context, in *ListRequest, opts ...client.CallOption) (*ListResponse, error)
}
type rulesService struct {
c client.Client
name string
}
func NewRulesService(name string, c client.Client) RulesService {
return &rulesService{
c: c,
name: name,
}
}
func (c *rulesService) Create(ctx context.Context, in *CreateRequest, opts ...client.CallOption) (*CreateResponse, error) {
req := c.c.NewRequest(c.name, "Rules.Create", in)
out := new(CreateResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *rulesService) Delete(ctx context.Context, in *DeleteRequest, opts ...client.CallOption) (*DeleteResponse, error) {
req := c.c.NewRequest(c.name, "Rules.Delete", in)
out := new(DeleteResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *rulesService) List(ctx context.Context, in *ListRequest, opts ...client.CallOption) (*ListResponse, error) {
req := c.c.NewRequest(c.name, "Rules.List", in)
out := new(ListResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// Server API for Rules service
type RulesHandler interface {
Create(context.Context, *CreateRequest, *CreateResponse) error
Delete(context.Context, *DeleteRequest, *DeleteResponse) error
List(context.Context, *ListRequest, *ListResponse) error
}
func RegisterRulesHandler(s server.Server, hdlr RulesHandler, opts ...server.HandlerOption) error {
type rules interface {
Create(ctx context.Context, in *CreateRequest, out *CreateResponse) error
Delete(ctx context.Context, in *DeleteRequest, out *DeleteResponse) error
List(ctx context.Context, in *ListRequest, out *ListResponse) error
}
type Rules struct {
rules
}
h := &rulesHandler{hdlr}
return s.Handle(s.NewHandler(&Rules{h}, opts...))
}
type rulesHandler struct {
RulesHandler
}
func (h *rulesHandler) Create(ctx context.Context, in *CreateRequest, out *CreateResponse) error {
return h.RulesHandler.Create(ctx, in, out)
}
func (h *rulesHandler) Delete(ctx context.Context, in *DeleteRequest, out *DeleteResponse) error {
return h.RulesHandler.Delete(ctx, in, out)
}
func (h *rulesHandler) List(ctx context.Context, in *ListRequest, out *ListResponse) error {
return h.RulesHandler.List(ctx, in, out)
}

View File

@ -1,148 +0,0 @@
syntax = "proto3";
package go.micro.auth;
service Auth {
rpc Generate(GenerateRequest) returns (GenerateResponse) {};
rpc Inspect(InspectRequest) returns (InspectResponse) {};
rpc Token(TokenRequest) returns (TokenResponse) {};
}
service Accounts {
rpc List(ListAccountsRequest) returns (ListAccountsResponse) {};
rpc Delete(DeleteAccountRequest) returns (DeleteAccountResponse) {};
}
service Rules {
rpc Create(CreateRequest) returns (CreateResponse) {};
rpc Delete(DeleteRequest) returns (DeleteResponse) {};
rpc List(ListRequest) returns (ListResponse) {};
}
message ListAccountsRequest {
Options options = 1;
}
message ListAccountsResponse {
repeated Account accounts = 1;
}
message DeleteAccountRequest {
string id = 1;
Options options = 2;
}
message DeleteAccountResponse {}
message Token {
string access_token = 1;
string refresh_token = 2;
int64 created = 3;
int64 expiry = 4;
}
message Account {
string id = 1;
string type = 2;
map<string, string> metadata = 4;
repeated string scopes = 5;
string issuer = 6;
string secret = 7;
}
message Resource{
string name = 1;
string type = 2;
string endpoint = 3;
}
message GenerateRequest {
string id = 1;
map<string, string> metadata = 3;
repeated string scopes = 4;
string secret = 5;
string type = 6;
string provider = 7;
Options options = 8;
}
message GenerateResponse {
Account account = 1;
}
message GrantRequest {
string scope = 1;
Resource resource = 2;
Options options = 3;
}
message GrantResponse {}
message RevokeRequest {
string scope = 1;
Resource resource = 2;
Options options = 3;
}
message RevokeResponse {}
message InspectRequest {
string token = 1;
Options options = 2;
}
message InspectResponse {
Account account = 1;
}
message TokenRequest {
string id = 1;
string secret = 2;
string refresh_token = 3;
int64 token_expiry = 4;
Options options = 5;
}
message TokenResponse {
Token token = 1;
}
enum Access {
UNKNOWN = 0;
GRANTED = 1;
DENIED = 2;
}
message Rule {
string id = 1;
string scope = 2;
Resource resource = 3;
Access access = 4;
int32 priority = 5;
}
message Options {
string namespace = 1;
}
message CreateRequest {
Rule rule = 1;
Options options = 2;
}
message CreateResponse {}
message DeleteRequest {
string id = 1;
Options options = 2;
}
message DeleteResponse {}
message ListRequest {
Options options = 2;
}
message ListResponse {
repeated Rule rules = 1;
}

View File

@ -1,325 +0,0 @@
// Package servie is a micro service implementation of the auth interface
package service
import (
"context"
"strings"
"time"
"github.com/micro/go-micro/v2/auth"
pb "github.com/micro/go-micro/v2/auth/service/proto"
"github.com/micro/go-micro/v2/client"
"github.com/micro/go-micro/v2/util/token"
"github.com/micro/go-micro/v2/util/token/jwt"
)
// svc is the service implementation of the Auth interface
type svcAuth struct {
options auth.Options
auth pb.AuthService
rules pb.RulesService
token token.Provider
}
func (s *svcAuth) String() string {
return "service"
}
func (s *svcAuth) Init(opts ...auth.Option) {
for _, o := range opts {
o(&s.options)
}
s.auth = pb.NewAuthService("go.micro.auth", s.options.Client)
s.rules = pb.NewRulesService("go.micro.auth", s.options.Client)
s.setupJWT()
}
func (s *svcAuth) Options() auth.Options {
return s.options
}
// Generate a new account
func (s *svcAuth) Generate(id string, opts ...auth.GenerateOption) (*auth.Account, error) {
options := auth.NewGenerateOptions(opts...)
if len(options.Issuer) == 0 {
options.Issuer = s.options.Issuer
}
// we have the JWT private key and generate ourselves an account
if len(s.options.PrivateKey) > 0 {
acc := &auth.Account{
ID: id,
Type: options.Type,
Scopes: options.Scopes,
Metadata: options.Metadata,
Issuer: options.Issuer,
}
tok, err := s.token.Generate(acc, token.WithExpiry(time.Hour*24*365))
if err != nil {
return nil, err
}
// when using JWTs, the account secret is the JWT's token. This
// can be used as an argument in the Token method.
acc.Secret = tok.Token
return acc, nil
}
rsp, err := s.auth.Generate(context.TODO(), &pb.GenerateRequest{
Id: id,
Type: options.Type,
Secret: options.Secret,
Scopes: options.Scopes,
Metadata: options.Metadata,
Provider: options.Provider,
Options: &pb.Options{
Namespace: options.Issuer,
},
}, s.callOpts()...)
if err != nil {
return nil, err
}
return serializeAccount(rsp.Account), nil
}
// Grant access to a resource
func (s *svcAuth) Grant(rule *auth.Rule) error {
access := pb.Access_UNKNOWN
if rule.Access == auth.AccessGranted {
access = pb.Access_GRANTED
} else if rule.Access == auth.AccessDenied {
access = pb.Access_DENIED
}
_, err := s.rules.Create(context.TODO(), &pb.CreateRequest{
Rule: &pb.Rule{
Id: rule.ID,
Scope: rule.Scope,
Priority: rule.Priority,
Access: access,
Resource: &pb.Resource{
Type: rule.Resource.Type,
Name: rule.Resource.Name,
Endpoint: rule.Resource.Endpoint,
},
},
Options: &pb.Options{
Namespace: s.Options().Issuer,
},
}, s.callOpts()...)
return err
}
// Revoke access to a resource
func (s *svcAuth) Revoke(rule *auth.Rule) error {
_, err := s.rules.Delete(context.TODO(), &pb.DeleteRequest{
Id: rule.ID, Options: &pb.Options{
Namespace: s.Options().Issuer,
},
}, s.callOpts()...)
return err
}
func (s *svcAuth) Rules(opts ...auth.RulesOption) ([]*auth.Rule, error) {
var options auth.RulesOptions
for _, o := range opts {
o(&options)
}
if options.Context == nil {
options.Context = context.TODO()
}
if len(options.Namespace) == 0 {
options.Namespace = s.options.Issuer
}
callOpts := append(s.callOpts(), client.WithCache(time.Second*30))
rsp, err := s.rules.List(options.Context, &pb.ListRequest{
Options: &pb.Options{Namespace: options.Namespace},
}, callOpts...)
if err != nil {
return nil, err
}
rules := make([]*auth.Rule, len(rsp.Rules))
for i, r := range rsp.Rules {
rules[i] = serializeRule(r)
}
return rules, nil
}
// Verify an account has access to a resource
func (s *svcAuth) Verify(acc *auth.Account, res *auth.Resource, opts ...auth.VerifyOption) error {
var options auth.VerifyOptions
for _, o := range opts {
o(&options)
}
rs, err := s.Rules(
auth.RulesContext(options.Context),
auth.RulesNamespace(options.Namespace),
)
if err != nil {
return err
}
return auth.VerifyAccess(rs, acc, res)
}
// Inspect a token
func (s *svcAuth) Inspect(token string) (*auth.Account, error) {
// try to decode JWT locally and fall back to srv if an error occurs
if len(strings.Split(token, ".")) == 3 && len(s.options.PublicKey) > 0 {
return s.token.Inspect(token)
}
// the token is not a JWT or we do not have the keys to decode it,
// fall back to the auth service
rsp, err := s.auth.Inspect(context.TODO(), &pb.InspectRequest{
Token: token, Options: &pb.Options{Namespace: s.Options().Issuer},
}, s.callOpts()...)
if err != nil {
return nil, err
}
return serializeAccount(rsp.Account), nil
}
// Token generation using an account ID and secret
func (s *svcAuth) Token(opts ...auth.TokenOption) (*auth.Token, error) {
options := auth.NewTokenOptions(opts...)
if len(options.Issuer) == 0 {
options.Issuer = s.options.Issuer
}
// we have the JWT private key and refresh accounts locally
if len(s.options.PrivateKey) > 0 {
tok := options.RefreshToken
if len(options.Secret) > 0 {
tok = options.Secret
}
acc, err := s.token.Inspect(tok)
if err != nil {
return nil, err
}
token, err := s.token.Generate(acc, token.WithExpiry(options.Expiry))
if err != nil {
return nil, err
}
return &auth.Token{
Expiry: token.Expiry,
AccessToken: token.Token,
RefreshToken: tok,
}, nil
}
rsp, err := s.auth.Token(context.Background(), &pb.TokenRequest{
Id: options.ID,
Secret: options.Secret,
RefreshToken: options.RefreshToken,
TokenExpiry: int64(options.Expiry.Seconds()),
Options: &pb.Options{
Namespace: options.Issuer,
},
}, s.callOpts()...)
if err != nil {
return nil, err
}
return serializeToken(rsp.Token), nil
}
func serializeToken(t *pb.Token) *auth.Token {
return &auth.Token{
AccessToken: t.AccessToken,
RefreshToken: t.RefreshToken,
Created: time.Unix(t.Created, 0),
Expiry: time.Unix(t.Expiry, 0),
}
}
func serializeAccount(a *pb.Account) *auth.Account {
return &auth.Account{
ID: a.Id,
Secret: a.Secret,
Issuer: a.Issuer,
Metadata: a.Metadata,
Scopes: a.Scopes,
}
}
func serializeRule(r *pb.Rule) *auth.Rule {
var access auth.Access
if r.Access == pb.Access_GRANTED {
access = auth.AccessGranted
} else {
access = auth.AccessDenied
}
return &auth.Rule{
ID: r.Id,
Scope: r.Scope,
Access: access,
Priority: r.Priority,
Resource: &auth.Resource{
Type: r.Resource.Type,
Name: r.Resource.Name,
Endpoint: r.Resource.Endpoint,
},
}
}
func (s *svcAuth) callOpts() []client.CallOption {
return []client.CallOption{
client.WithAddress(s.options.Addrs...),
}
}
// NewAuth returns a new instance of the Auth service
func NewAuth(opts ...auth.Option) auth.Auth {
options := auth.NewOptions(opts...)
if options.Client == nil {
options.Client = client.DefaultClient
}
if len(options.Addrs) == 0 {
options.Addrs = []string{"127.0.0.1:8010"}
}
service := &svcAuth{
auth: pb.NewAuthService("go.micro.auth", options.Client),
rules: pb.NewRulesService("go.micro.auth", options.Client),
options: options,
}
service.setupJWT()
return service
}
func (s *svcAuth) setupJWT() {
tokenOpts := []token.Option{}
// if we have a JWT public key passed as an option,
// we can decode tokens with the type "JWT" locally
// and not have to make an RPC call
if key := s.options.PublicKey; len(key) > 0 {
tokenOpts = append(tokenOpts, token.WithPublicKey(key))
}
// if we have a JWT private key passed as an option,
// we can generate accounts locally and not have to make
// an RPC call, this is used for micro clients such as
// api, web, proxy.
if key := s.options.PrivateKey; len(key) > 0 {
tokenOpts = append(tokenOpts, token.WithPrivateKey(key))
}
s.token = jwt.NewTokenProvider(tokenOpts...)
}

View File

@ -37,31 +37,3 @@ type Subscriber interface {
Topic() string Topic() string
Unsubscribe() error Unsubscribe() error
} }
var (
DefaultBroker Broker = NewBroker()
)
func Init(opts ...Option) error {
return DefaultBroker.Init(opts...)
}
func Connect() error {
return DefaultBroker.Connect()
}
func Disconnect() error {
return DefaultBroker.Disconnect()
}
func Publish(topic string, msg *Message, opts ...PublishOption) error {
return DefaultBroker.Publish(topic, msg, opts...)
}
func Subscribe(topic string, handler Handler, opts ...SubscribeOption) (Subscriber, error) {
return DefaultBroker.Subscribe(topic, handler, opts...)
}
func String() string {
return DefaultBroker.String()
}

View File

@ -1,711 +0,0 @@
// Package http provides a http based message broker
package broker
import (
"bytes"
"context"
"crypto/tls"
"errors"
"fmt"
"io"
"io/ioutil"
"math/rand"
"net"
"net/http"
"net/url"
"runtime"
"sync"
"time"
"github.com/google/uuid"
"github.com/micro/go-micro/v2/codec/json"
merr "github.com/micro/go-micro/v2/errors"
"github.com/micro/go-micro/v2/registry"
"github.com/micro/go-micro/v2/registry/cache"
maddr "github.com/micro/go-micro/v2/util/addr"
mnet "github.com/micro/go-micro/v2/util/net"
mls "github.com/micro/go-micro/v2/util/tls"
"golang.org/x/net/http2"
)
// HTTP Broker is a point to point async broker
type httpBroker struct {
id string
address string
opts Options
mux *http.ServeMux
c *http.Client
r registry.Registry
sync.RWMutex
subscribers map[string][]*httpSubscriber
running bool
exit chan chan error
// offline message inbox
mtx sync.RWMutex
inbox map[string][][]byte
}
type httpSubscriber struct {
opts SubscribeOptions
id string
topic string
fn Handler
svc *registry.Service
hb *httpBroker
}
type httpEvent struct {
m *Message
t string
err error
}
var (
DefaultPath = "/"
DefaultAddress = "127.0.0.1:0"
serviceName = "micro.http.broker"
broadcastVersion = "ff.http.broadcast"
registerTTL = time.Minute
registerInterval = time.Second * 30
)
func init() {
rand.Seed(time.Now().Unix())
}
func newTransport(config *tls.Config) *http.Transport {
if config == nil {
config = &tls.Config{
InsecureSkipVerify: true,
}
}
dialTLS := func(network string, addr string) (net.Conn, error) {
return tls.Dial(network, addr, config)
}
t := &http.Transport{
Proxy: http.ProxyFromEnvironment,
Dial: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).Dial,
TLSHandshakeTimeout: 10 * time.Second,
DialTLS: dialTLS,
}
runtime.SetFinalizer(&t, func(tr **http.Transport) {
(*tr).CloseIdleConnections()
})
// setup http2
http2.ConfigureTransport(t)
return t
}
func newHttpBroker(opts ...Option) Broker {
options := Options{
Codec: json.Marshaler{},
Context: context.TODO(),
Registry: registry.DefaultRegistry,
}
for _, o := range opts {
o(&options)
}
// set address
addr := DefaultAddress
if len(options.Addrs) > 0 && len(options.Addrs[0]) > 0 {
addr = options.Addrs[0]
}
h := &httpBroker{
id: uuid.New().String(),
address: addr,
opts: options,
r: options.Registry,
c: &http.Client{Transport: newTransport(options.TLSConfig)},
subscribers: make(map[string][]*httpSubscriber),
exit: make(chan chan error),
mux: http.NewServeMux(),
inbox: make(map[string][][]byte),
}
// specify the message handler
h.mux.Handle(DefaultPath, h)
// get optional handlers
if h.opts.Context != nil {
handlers, ok := h.opts.Context.Value("http_handlers").(map[string]http.Handler)
if ok {
for pattern, handler := range handlers {
h.mux.Handle(pattern, handler)
}
}
}
return h
}
func (h *httpEvent) Ack() error {
return nil
}
func (h *httpEvent) Error() error {
return h.err
}
func (h *httpEvent) Message() *Message {
return h.m
}
func (h *httpEvent) Topic() string {
return h.t
}
func (h *httpSubscriber) Options() SubscribeOptions {
return h.opts
}
func (h *httpSubscriber) Topic() string {
return h.topic
}
func (h *httpSubscriber) Unsubscribe() error {
return h.hb.unsubscribe(h)
}
func (h *httpBroker) saveMessage(topic string, msg []byte) {
h.mtx.Lock()
defer h.mtx.Unlock()
// get messages
c := h.inbox[topic]
// save message
c = append(c, msg)
// max length 64
if len(c) > 64 {
c = c[:64]
}
// save inbox
h.inbox[topic] = c
}
func (h *httpBroker) getMessage(topic string, num int) [][]byte {
h.mtx.Lock()
defer h.mtx.Unlock()
// get messages
c, ok := h.inbox[topic]
if !ok {
return nil
}
// more message than requests
if len(c) >= num {
msg := c[:num]
h.inbox[topic] = c[num:]
return msg
}
// reset inbox
h.inbox[topic] = nil
// return all messages
return c
}
func (h *httpBroker) subscribe(s *httpSubscriber) error {
h.Lock()
defer h.Unlock()
if err := h.r.Register(s.svc, registry.RegisterTTL(registerTTL)); err != nil {
return err
}
h.subscribers[s.topic] = append(h.subscribers[s.topic], s)
return nil
}
func (h *httpBroker) unsubscribe(s *httpSubscriber) error {
h.Lock()
defer h.Unlock()
//nolint:prealloc
var subscribers []*httpSubscriber
// look for subscriber
for _, sub := range h.subscribers[s.topic] {
// deregister and skip forward
if sub == s {
_ = h.r.Deregister(sub.svc)
continue
}
// keep subscriber
subscribers = append(subscribers, sub)
}
// set subscribers
h.subscribers[s.topic] = subscribers
return nil
}
func (h *httpBroker) run(l net.Listener) {
t := time.NewTicker(registerInterval)
defer t.Stop()
for {
select {
// heartbeat for each subscriber
case <-t.C:
h.RLock()
for _, subs := range h.subscribers {
for _, sub := range subs {
_ = h.r.Register(sub.svc, registry.RegisterTTL(registerTTL))
}
}
h.RUnlock()
// received exit signal
case ch := <-h.exit:
ch <- l.Close()
h.RLock()
for _, subs := range h.subscribers {
for _, sub := range subs {
_ = h.r.Deregister(sub.svc)
}
}
h.RUnlock()
return
}
}
}
func (h *httpBroker) ServeHTTP(w http.ResponseWriter, req *http.Request) {
if req.Method != "POST" {
err := merr.BadRequest("go.micro.broker", "Method not allowed")
http.Error(w, err.Error(), http.StatusMethodNotAllowed)
return
}
defer req.Body.Close()
req.ParseForm()
b, err := ioutil.ReadAll(req.Body)
if err != nil {
errr := merr.InternalServerError("go.micro.broker", "Error reading request body: %v", err)
w.WriteHeader(500)
w.Write([]byte(errr.Error()))
return
}
var m *Message
if err = h.opts.Codec.Unmarshal(b, &m); err != nil {
errr := merr.InternalServerError("go.micro.broker", "Error parsing request body: %v", err)
w.WriteHeader(500)
w.Write([]byte(errr.Error()))
return
}
topic := m.Header["Micro-Topic"]
//delete(m.Header, ":topic")
if len(topic) == 0 {
errr := merr.InternalServerError("go.micro.broker", "Topic not found")
w.WriteHeader(500)
w.Write([]byte(errr.Error()))
return
}
p := &httpEvent{m: m, t: topic}
id := req.Form.Get("id")
//nolint:prealloc
var subs []Handler
h.RLock()
for _, subscriber := range h.subscribers[topic] {
if id != subscriber.id {
continue
}
subs = append(subs, subscriber.fn)
}
h.RUnlock()
// execute the handler
for _, fn := range subs {
p.err = fn(p)
}
}
func (h *httpBroker) Address() string {
h.RLock()
defer h.RUnlock()
return h.address
}
func (h *httpBroker) Connect() error {
h.RLock()
if h.running {
h.RUnlock()
return nil
}
h.RUnlock()
h.Lock()
defer h.Unlock()
var l net.Listener
var err error
if h.opts.Secure || h.opts.TLSConfig != nil {
config := h.opts.TLSConfig
fn := func(addr string) (net.Listener, error) {
if config == nil {
hosts := []string{addr}
// check if its a valid host:port
if host, _, err := net.SplitHostPort(addr); err == nil {
if len(host) == 0 {
hosts = maddr.IPs()
} else {
hosts = []string{host}
}
}
// generate a certificate
cert, err := mls.Certificate(hosts...)
if err != nil {
return nil, err
}
config = &tls.Config{Certificates: []tls.Certificate{cert}}
}
return tls.Listen("tcp", addr, config)
}
l, err = mnet.Listen(h.address, fn)
} else {
fn := func(addr string) (net.Listener, error) {
return net.Listen("tcp", addr)
}
l, err = mnet.Listen(h.address, fn)
}
if err != nil {
return err
}
addr := h.address
h.address = l.Addr().String()
go http.Serve(l, h.mux)
go func() {
h.run(l)
h.Lock()
h.opts.Addrs = []string{addr}
h.address = addr
h.Unlock()
}()
// get registry
reg := h.opts.Registry
if reg == nil {
reg = registry.DefaultRegistry
}
// set cache
h.r = cache.New(reg)
// set running
h.running = true
return nil
}
func (h *httpBroker) Disconnect() error {
h.RLock()
if !h.running {
h.RUnlock()
return nil
}
h.RUnlock()
h.Lock()
defer h.Unlock()
// stop cache
rc, ok := h.r.(cache.Cache)
if ok {
rc.Stop()
}
// exit and return err
ch := make(chan error)
h.exit <- ch
err := <-ch
// set not running
h.running = false
return err
}
func (h *httpBroker) Init(opts ...Option) error {
h.RLock()
if h.running {
h.RUnlock()
return errors.New("cannot init while connected")
}
h.RUnlock()
h.Lock()
defer h.Unlock()
for _, o := range opts {
o(&h.opts)
}
if len(h.opts.Addrs) > 0 && len(h.opts.Addrs[0]) > 0 {
h.address = h.opts.Addrs[0]
}
if len(h.id) == 0 {
h.id = "go.micro.http.broker-" + uuid.New().String()
}
// get registry
reg := h.opts.Registry
if reg == nil {
reg = registry.DefaultRegistry
}
// get cache
if rc, ok := h.r.(cache.Cache); ok {
rc.Stop()
}
// set registry
h.r = cache.New(reg)
// reconfigure tls config
if c := h.opts.TLSConfig; c != nil {
h.c = &http.Client{
Transport: newTransport(c),
}
}
return nil
}
func (h *httpBroker) Options() Options {
return h.opts
}
func (h *httpBroker) Publish(topic string, msg *Message, opts ...PublishOption) error {
// create the message first
m := &Message{
Header: make(map[string]string),
Body: msg.Body,
}
for k, v := range msg.Header {
m.Header[k] = v
}
m.Header["Micro-Topic"] = topic
// encode the message
b, err := h.opts.Codec.Marshal(m)
if err != nil {
return err
}
// save the message
h.saveMessage(topic, b)
// now attempt to get the service
h.RLock()
s, err := h.r.GetService(serviceName)
if err != nil {
h.RUnlock()
return err
}
h.RUnlock()
pub := func(node *registry.Node, t string, b []byte) error {
scheme := "http"
// check if secure is added in metadata
if node.Metadata["secure"] == "true" {
scheme = "https"
}
vals := url.Values{}
vals.Add("id", node.Id)
uri := fmt.Sprintf("%s://%s%s?%s", scheme, node.Address, DefaultPath, vals.Encode())
r, err := h.c.Post(uri, "application/json", bytes.NewReader(b))
if err != nil {
return err
}
// discard response body
io.Copy(ioutil.Discard, r.Body)
r.Body.Close()
return nil
}
srv := func(s []*registry.Service, b []byte) {
for _, service := range s {
var nodes []*registry.Node
for _, node := range service.Nodes {
// only use nodes tagged with broker http
if node.Metadata["broker"] != "http" {
continue
}
// look for nodes for the topic
if node.Metadata["topic"] != topic {
continue
}
nodes = append(nodes, node)
}
// only process if we have nodes
if len(nodes) == 0 {
continue
}
switch service.Version {
// broadcast version means broadcast to all nodes
case broadcastVersion:
var success bool
// publish to all nodes
for _, node := range nodes {
// publish async
if err := pub(node, topic, b); err == nil {
success = true
}
}
// save if it failed to publish at least once
if !success {
h.saveMessage(topic, b)
}
default:
// select node to publish to
node := nodes[rand.Int()%len(nodes)]
// publish async to one node
if err := pub(node, topic, b); err != nil {
// if failed save it
h.saveMessage(topic, b)
}
}
}
}
// do the rest async
go func() {
// get a third of the backlog
messages := h.getMessage(topic, 8)
delay := (len(messages) > 1)
// publish all the messages
for _, msg := range messages {
// serialize here
srv(s, msg)
// sending a backlog of messages
if delay {
time.Sleep(time.Millisecond * 100)
}
}
}()
return nil
}
func (h *httpBroker) Subscribe(topic string, handler Handler, opts ...SubscribeOption) (Subscriber, error) {
var err error
var host, port string
options := NewSubscribeOptions(opts...)
// parse address for host, port
host, port, err = net.SplitHostPort(h.Address())
if err != nil {
return nil, err
}
addr, err := maddr.Extract(host)
if err != nil {
return nil, err
}
var secure bool
if h.opts.Secure || h.opts.TLSConfig != nil {
secure = true
}
// register service
node := &registry.Node{
Id: topic + "-" + h.id,
Address: mnet.HostPort(addr, port),
Metadata: map[string]string{
"secure": fmt.Sprintf("%t", secure),
"broker": "http",
"topic": topic,
},
}
// check for queue group or broadcast queue
version := options.Queue
if len(version) == 0 {
version = broadcastVersion
}
service := &registry.Service{
Name: serviceName,
Version: version,
Nodes: []*registry.Node{node},
}
// generate subscriber
subscriber := &httpSubscriber{
opts: options,
hb: h,
id: node.Id,
topic: topic,
fn: handler,
svc: service,
}
// subscribe now
if err := h.subscribe(subscriber); err != nil {
return nil, err
}
// return the subscriber
return subscriber, nil
}
func (h *httpBroker) String() string {
return "http"
}
// NewBroker returns a new http broker
func NewBroker(opts ...Option) Broker {
return newHttpBroker(opts...)
}

View File

@ -2,10 +2,712 @@
package http package http
import ( import (
"github.com/micro/go-micro/v2/broker" "bytes"
"context"
"crypto/tls"
"errors"
"fmt"
"io"
"io/ioutil"
"math/rand"
"net"
"net/http"
"net/url"
"runtime"
"sync"
"time"
"github.com/google/uuid"
"github.com/micro/go-micro/v3/broker"
"github.com/micro/go-micro/v3/codec/json"
merr "github.com/micro/go-micro/v3/errors"
"github.com/micro/go-micro/v3/registry"
"github.com/micro/go-micro/v3/registry/cache"
"github.com/micro/go-micro/v3/registry/mdns"
maddr "github.com/micro/go-micro/v3/util/addr"
mnet "github.com/micro/go-micro/v3/util/net"
mls "github.com/micro/go-micro/v3/util/tls"
"golang.org/x/net/http2"
) )
// HTTP Broker is a point to point async broker
type httpBroker struct {
id string
address string
opts broker.Options
mux *http.ServeMux
c *http.Client
r registry.Registry
sync.RWMutex
subscribers map[string][]*httpSubscriber
running bool
exit chan chan error
// offline message inbox
mtx sync.RWMutex
inbox map[string][][]byte
}
type httpSubscriber struct {
opts broker.SubscribeOptions
id string
topic string
fn broker.Handler
svc *registry.Service
hb *httpBroker
}
type httpEvent struct {
m *broker.Message
t string
err error
}
var (
DefaultPath = "/"
DefaultAddress = "127.0.0.1:0"
serviceName = "micro.http.broker"
broadcastVersion = "ff.http.broadcast"
registerTTL = time.Minute
registerInterval = time.Second * 30
)
func init() {
rand.Seed(time.Now().Unix())
}
func newTransport(config *tls.Config) *http.Transport {
if config == nil {
config = &tls.Config{
InsecureSkipVerify: true,
}
}
dialTLS := func(network string, addr string) (net.Conn, error) {
return tls.Dial(network, addr, config)
}
t := &http.Transport{
Proxy: http.ProxyFromEnvironment,
Dial: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).Dial,
TLSHandshakeTimeout: 10 * time.Second,
DialTLS: dialTLS,
}
runtime.SetFinalizer(&t, func(tr **http.Transport) {
(*tr).CloseIdleConnections()
})
// setup http2
http2.ConfigureTransport(t)
return t
}
func newHttpBroker(opts ...broker.Option) broker.Broker {
options := broker.Options{
Codec: json.Marshaler{},
Context: context.TODO(),
Registry: mdns.NewRegistry(),
}
for _, o := range opts {
o(&options)
}
// set address
addr := DefaultAddress
if len(options.Addrs) > 0 && len(options.Addrs[0]) > 0 {
addr = options.Addrs[0]
}
h := &httpBroker{
id: uuid.New().String(),
address: addr,
opts: options,
r: options.Registry,
c: &http.Client{Transport: newTransport(options.TLSConfig)},
subscribers: make(map[string][]*httpSubscriber),
exit: make(chan chan error),
mux: http.NewServeMux(),
inbox: make(map[string][][]byte),
}
// specify the message handler
h.mux.Handle(DefaultPath, h)
// get optional handlers
if h.opts.Context != nil {
handlers, ok := h.opts.Context.Value("http_handlers").(map[string]http.Handler)
if ok {
for pattern, handler := range handlers {
h.mux.Handle(pattern, handler)
}
}
}
return h
}
func (h *httpEvent) Ack() error {
return nil
}
func (h *httpEvent) Error() error {
return h.err
}
func (h *httpEvent) Message() *broker.Message {
return h.m
}
func (h *httpEvent) Topic() string {
return h.t
}
func (h *httpSubscriber) Options() broker.SubscribeOptions {
return h.opts
}
func (h *httpSubscriber) Topic() string {
return h.topic
}
func (h *httpSubscriber) Unsubscribe() error {
return h.hb.unsubscribe(h)
}
func (h *httpBroker) saveMessage(topic string, msg []byte) {
h.mtx.Lock()
defer h.mtx.Unlock()
// get messages
c := h.inbox[topic]
// save message
c = append(c, msg)
// max length 64
if len(c) > 64 {
c = c[:64]
}
// save inbox
h.inbox[topic] = c
}
func (h *httpBroker) getMessage(topic string, num int) [][]byte {
h.mtx.Lock()
defer h.mtx.Unlock()
// get messages
c, ok := h.inbox[topic]
if !ok {
return nil
}
// more message than requests
if len(c) >= num {
msg := c[:num]
h.inbox[topic] = c[num:]
return msg
}
// reset inbox
h.inbox[topic] = nil
// return all messages
return c
}
func (h *httpBroker) subscribe(s *httpSubscriber) error {
h.Lock()
defer h.Unlock()
if err := h.r.Register(s.svc, registry.RegisterTTL(registerTTL)); err != nil {
return err
}
h.subscribers[s.topic] = append(h.subscribers[s.topic], s)
return nil
}
func (h *httpBroker) unsubscribe(s *httpSubscriber) error {
h.Lock()
defer h.Unlock()
//nolint:prealloc
var subscribers []*httpSubscriber
// look for subscriber
for _, sub := range h.subscribers[s.topic] {
// deregister and skip forward
if sub == s {
_ = h.r.Deregister(sub.svc)
continue
}
// keep subscriber
subscribers = append(subscribers, sub)
}
// set subscribers
h.subscribers[s.topic] = subscribers
return nil
}
func (h *httpBroker) run(l net.Listener) {
t := time.NewTicker(registerInterval)
defer t.Stop()
for {
select {
// heartbeat for each subscriber
case <-t.C:
h.RLock()
for _, subs := range h.subscribers {
for _, sub := range subs {
_ = h.r.Register(sub.svc, registry.RegisterTTL(registerTTL))
}
}
h.RUnlock()
// received exit signal
case ch := <-h.exit:
ch <- l.Close()
h.RLock()
for _, subs := range h.subscribers {
for _, sub := range subs {
_ = h.r.Deregister(sub.svc)
}
}
h.RUnlock()
return
}
}
}
func (h *httpBroker) ServeHTTP(w http.ResponseWriter, req *http.Request) {
if req.Method != "POST" {
err := merr.BadRequest("go.micro.broker", "Method not allowed")
http.Error(w, err.Error(), http.StatusMethodNotAllowed)
return
}
defer req.Body.Close()
req.ParseForm()
b, err := ioutil.ReadAll(req.Body)
if err != nil {
errr := merr.InternalServerError("go.micro.broker", "Error reading request body: %v", err)
w.WriteHeader(500)
w.Write([]byte(errr.Error()))
return
}
var m *broker.Message
if err = h.opts.Codec.Unmarshal(b, &m); err != nil {
errr := merr.InternalServerError("go.micro.broker", "Error parsing request body: %v", err)
w.WriteHeader(500)
w.Write([]byte(errr.Error()))
return
}
topic := m.Header["Micro-Topic"]
//delete(m.Header, ":topic")
if len(topic) == 0 {
errr := merr.InternalServerError("go.micro.broker", "Topic not found")
w.WriteHeader(500)
w.Write([]byte(errr.Error()))
return
}
p := &httpEvent{m: m, t: topic}
id := req.Form.Get("id")
//nolint:prealloc
var subs []broker.Handler
h.RLock()
for _, subscriber := range h.subscribers[topic] {
if id != subscriber.id {
continue
}
subs = append(subs, subscriber.fn)
}
h.RUnlock()
// execute the handler
for _, fn := range subs {
p.err = fn(p)
}
}
func (h *httpBroker) Address() string {
h.RLock()
defer h.RUnlock()
return h.address
}
func (h *httpBroker) Connect() error {
h.RLock()
if h.running {
h.RUnlock()
return nil
}
h.RUnlock()
h.Lock()
defer h.Unlock()
var l net.Listener
var err error
if h.opts.Secure || h.opts.TLSConfig != nil {
config := h.opts.TLSConfig
fn := func(addr string) (net.Listener, error) {
if config == nil {
hosts := []string{addr}
// check if its a valid host:port
if host, _, err := net.SplitHostPort(addr); err == nil {
if len(host) == 0 {
hosts = maddr.IPs()
} else {
hosts = []string{host}
}
}
// generate a certificate
cert, err := mls.Certificate(hosts...)
if err != nil {
return nil, err
}
config = &tls.Config{Certificates: []tls.Certificate{cert}}
}
return tls.Listen("tcp", addr, config)
}
l, err = mnet.Listen(h.address, fn)
} else {
fn := func(addr string) (net.Listener, error) {
return net.Listen("tcp", addr)
}
l, err = mnet.Listen(h.address, fn)
}
if err != nil {
return err
}
addr := h.address
h.address = l.Addr().String()
go http.Serve(l, h.mux)
go func() {
h.run(l)
h.Lock()
h.opts.Addrs = []string{addr}
h.address = addr
h.Unlock()
}()
// get registry
reg := h.opts.Registry
if reg == nil {
reg = mdns.NewRegistry()
}
// set cache
h.r = cache.New(reg)
// set running
h.running = true
return nil
}
func (h *httpBroker) Disconnect() error {
h.RLock()
if !h.running {
h.RUnlock()
return nil
}
h.RUnlock()
h.Lock()
defer h.Unlock()
// stop cache
rc, ok := h.r.(cache.Cache)
if ok {
rc.Stop()
}
// exit and return err
ch := make(chan error)
h.exit <- ch
err := <-ch
// set not running
h.running = false
return err
}
func (h *httpBroker) Init(opts ...broker.Option) error {
h.RLock()
if h.running {
h.RUnlock()
return errors.New("cannot init while connected")
}
h.RUnlock()
h.Lock()
defer h.Unlock()
for _, o := range opts {
o(&h.opts)
}
if len(h.opts.Addrs) > 0 && len(h.opts.Addrs[0]) > 0 {
h.address = h.opts.Addrs[0]
}
if len(h.id) == 0 {
h.id = "go.micro.http.broker-" + uuid.New().String()
}
// get registry
reg := h.opts.Registry
if reg == nil {
reg = mdns.NewRegistry()
}
// get cache
if rc, ok := h.r.(cache.Cache); ok {
rc.Stop()
}
// set registry
h.r = cache.New(reg)
// reconfigure tls config
if c := h.opts.TLSConfig; c != nil {
h.c = &http.Client{
Transport: newTransport(c),
}
}
return nil
}
func (h *httpBroker) Options() broker.Options {
return h.opts
}
func (h *httpBroker) Publish(topic string, msg *broker.Message, opts ...broker.PublishOption) error {
// create the message first
m := &broker.Message{
Header: make(map[string]string),
Body: msg.Body,
}
for k, v := range msg.Header {
m.Header[k] = v
}
m.Header["Micro-Topic"] = topic
// encode the message
b, err := h.opts.Codec.Marshal(m)
if err != nil {
return err
}
// save the message
h.saveMessage(topic, b)
// now attempt to get the service
h.RLock()
s, err := h.r.GetService(serviceName)
if err != nil {
h.RUnlock()
return err
}
h.RUnlock()
pub := func(node *registry.Node, t string, b []byte) error {
scheme := "http"
// check if secure is added in metadata
if node.Metadata["secure"] == "true" {
scheme = "https"
}
vals := url.Values{}
vals.Add("id", node.Id)
uri := fmt.Sprintf("%s://%s%s?%s", scheme, node.Address, DefaultPath, vals.Encode())
r, err := h.c.Post(uri, "application/json", bytes.NewReader(b))
if err != nil {
return err
}
// discard response body
io.Copy(ioutil.Discard, r.Body)
r.Body.Close()
return nil
}
srv := func(s []*registry.Service, b []byte) {
for _, service := range s {
var nodes []*registry.Node
for _, node := range service.Nodes {
// only use nodes tagged with broker http
if node.Metadata["broker"] != "http" {
continue
}
// look for nodes for the topic
if node.Metadata["topic"] != topic {
continue
}
nodes = append(nodes, node)
}
// only process if we have nodes
if len(nodes) == 0 {
continue
}
switch service.Version {
// broadcast version means broadcast to all nodes
case broadcastVersion:
var success bool
// publish to all nodes
for _, node := range nodes {
// publish async
if err := pub(node, topic, b); err == nil {
success = true
}
}
// save if it failed to publish at least once
if !success {
h.saveMessage(topic, b)
}
default:
// select node to publish to
node := nodes[rand.Int()%len(nodes)]
// publish async to one node
if err := pub(node, topic, b); err != nil {
// if failed save it
h.saveMessage(topic, b)
}
}
}
}
// do the rest async
go func() {
// get a third of the backlog
messages := h.getMessage(topic, 8)
delay := (len(messages) > 1)
// publish all the messages
for _, msg := range messages {
// serialize here
srv(s, msg)
// sending a backlog of messages
if delay {
time.Sleep(time.Millisecond * 100)
}
}
}()
return nil
}
func (h *httpBroker) Subscribe(topic string, handler broker.Handler, opts ...broker.SubscribeOption) (broker.Subscriber, error) {
var err error
var host, port string
options := broker.NewSubscribeOptions(opts...)
// parse address for host, port
host, port, err = net.SplitHostPort(h.Address())
if err != nil {
return nil, err
}
addr, err := maddr.Extract(host)
if err != nil {
return nil, err
}
var secure bool
if h.opts.Secure || h.opts.TLSConfig != nil {
secure = true
}
// register service
node := &registry.Node{
Id: topic + "-" + h.id,
Address: mnet.HostPort(addr, port),
Metadata: map[string]string{
"secure": fmt.Sprintf("%t", secure),
"broker": "http",
"topic": topic,
},
}
// check for queue group or broadcast queue
version := options.Queue
if len(version) == 0 {
version = broadcastVersion
}
service := &registry.Service{
Name: serviceName,
Version: version,
Nodes: []*registry.Node{node},
}
// generate subscriber
subscriber := &httpSubscriber{
opts: options,
hb: h,
id: node.Id,
topic: topic,
fn: handler,
svc: service,
}
// subscribe now
if err := h.subscribe(subscriber); err != nil {
return nil, err
}
// return the subscriber
return subscriber, nil
}
func (h *httpBroker) String() string {
return "http"
}
// NewBroker returns a new http broker // NewBroker returns a new http broker
func NewBroker(opts ...broker.Option) broker.Broker { func NewBroker(opts ...broker.Option) broker.Broker {
return broker.NewBroker(opts...) return newHttpBroker(opts...)
} }

View File

@ -1,4 +1,4 @@
package broker_test package http
import ( import (
"sync" "sync"
@ -6,9 +6,9 @@ import (
"time" "time"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/micro/go-micro/v2/broker" "github.com/micro/go-micro/v3/broker"
"github.com/micro/go-micro/v2/registry" "github.com/micro/go-micro/v3/registry"
"github.com/micro/go-micro/v2/registry/memory" "github.com/micro/go-micro/v3/registry/memory"
) )
var ( var (
@ -61,7 +61,7 @@ func sub(be *testing.B, c int) {
be.StopTimer() be.StopTimer()
m := newTestRegistry() m := newTestRegistry()
b := broker.NewBroker(broker.Registry(m)) b := NewBroker(broker.Registry(m))
topic := uuid.New().String() topic := uuid.New().String()
if err := b.Init(); err != nil { if err := b.Init(); err != nil {
@ -120,7 +120,7 @@ func sub(be *testing.B, c int) {
func pub(be *testing.B, c int) { func pub(be *testing.B, c int) {
be.StopTimer() be.StopTimer()
m := newTestRegistry() m := newTestRegistry()
b := broker.NewBroker(broker.Registry(m)) b := NewBroker(broker.Registry(m))
topic := uuid.New().String() topic := uuid.New().String()
if err := b.Init(); err != nil { if err := b.Init(); err != nil {
@ -189,7 +189,7 @@ func pub(be *testing.B, c int) {
func TestBroker(t *testing.T) { func TestBroker(t *testing.T) {
m := newTestRegistry() m := newTestRegistry()
b := broker.NewBroker(broker.Registry(m)) b := NewBroker(broker.Registry(m))
if err := b.Init(); err != nil { if err := b.Init(); err != nil {
t.Fatalf("Unexpected init error: %v", err) t.Fatalf("Unexpected init error: %v", err)
@ -236,7 +236,7 @@ func TestBroker(t *testing.T) {
func TestConcurrentSubBroker(t *testing.T) { func TestConcurrentSubBroker(t *testing.T) {
m := newTestRegistry() m := newTestRegistry()
b := broker.NewBroker(broker.Registry(m)) b := NewBroker(broker.Registry(m))
if err := b.Init(); err != nil { if err := b.Init(); err != nil {
t.Fatalf("Unexpected init error: %v", err) t.Fatalf("Unexpected init error: %v", err)
@ -293,7 +293,7 @@ func TestConcurrentSubBroker(t *testing.T) {
func TestConcurrentPubBroker(t *testing.T) { func TestConcurrentPubBroker(t *testing.T) {
m := newTestRegistry() m := newTestRegistry()
b := broker.NewBroker(broker.Registry(m)) b := NewBroker(broker.Registry(m))
if err := b.Init(); err != nil { if err := b.Init(); err != nil {
t.Fatalf("Unexpected init error: %v", err) t.Fatalf("Unexpected init error: %v", err)

View File

@ -4,7 +4,7 @@ import (
"context" "context"
"net/http" "net/http"
"github.com/micro/go-micro/v2/broker" "github.com/micro/go-micro/v3/broker"
) )
// Handle registers the handler for the given pattern. // Handle registers the handler for the given pattern.

View File

@ -9,10 +9,10 @@ import (
"time" "time"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/micro/go-micro/v2/broker" "github.com/micro/go-micro/v3/broker"
"github.com/micro/go-micro/v2/logger" "github.com/micro/go-micro/v3/logger"
maddr "github.com/micro/go-micro/v2/util/addr" maddr "github.com/micro/go-micro/v3/util/addr"
mnet "github.com/micro/go-micro/v2/util/net" mnet "github.com/micro/go-micro/v3/util/net"
) )
type memoryBroker struct { type memoryBroker struct {

View File

@ -4,7 +4,7 @@ import (
"fmt" "fmt"
"testing" "testing"
"github.com/micro/go-micro/v2/broker" "github.com/micro/go-micro/v3/broker"
) )
func TestMemoryBroker(t *testing.T) { func TestMemoryBroker(t *testing.T) {

View File

@ -3,7 +3,7 @@ package nats
import ( import (
"context" "context"
"github.com/micro/go-micro/v2/broker" "github.com/micro/go-micro/v3/broker"
) )
// setBrokerOption returns a function to setup a context with given value // setBrokerOption returns a function to setup a context with given value

View File

@ -7,10 +7,10 @@ import (
"strings" "strings"
"sync" "sync"
"github.com/micro/go-micro/v2/broker" "github.com/micro/go-micro/v3/broker"
"github.com/micro/go-micro/v2/codec/json" "github.com/micro/go-micro/v3/codec/json"
"github.com/micro/go-micro/v2/logger" "github.com/micro/go-micro/v3/logger"
"github.com/micro/go-micro/v2/registry" "github.com/micro/go-micro/v3/registry/mdns"
nats "github.com/nats-io/nats.go" nats "github.com/nats-io/nats.go"
) )
@ -310,7 +310,7 @@ func NewBroker(opts ...broker.Option) broker.Broker {
// Default codec // Default codec
Codec: json.Marshaler{}, Codec: json.Marshaler{},
Context: context.Background(), Context: context.Background(),
Registry: registry.DefaultRegistry, Registry: mdns.NewRegistry(),
} }
n := &natsBroker{ n := &natsBroker{

View File

@ -4,7 +4,7 @@ import (
"fmt" "fmt"
"testing" "testing"
"github.com/micro/go-micro/v2/broker" "github.com/micro/go-micro/v3/broker"
nats "github.com/nats-io/nats.go" nats "github.com/nats-io/nats.go"
) )

View File

@ -1,7 +1,7 @@
package nats package nats
import ( import (
"github.com/micro/go-micro/v2/broker" "github.com/micro/go-micro/v3/broker"
nats "github.com/nats-io/nats.go" nats "github.com/nats-io/nats.go"
) )

View File

@ -4,8 +4,8 @@ import (
"context" "context"
"crypto/tls" "crypto/tls"
"github.com/micro/go-micro/v2/codec" "github.com/micro/go-micro/v3/codec"
"github.com/micro/go-micro/v2/registry" "github.com/micro/go-micro/v3/registry"
) )
type Options struct { type Options struct {

View File

@ -1,22 +0,0 @@
package service
import (
"context"
"github.com/micro/go-micro/v2/broker"
"github.com/micro/go-micro/v2/client"
)
type clientKey struct{}
// Client to call broker service
func Client(c client.Client) broker.Option {
return func(o *broker.Options) {
if o.Context == nil {
o.Context = context.WithValue(context.Background(), clientKey{}, c)
return
}
o.Context = context.WithValue(o.Context, clientKey{}, c)
}
}

View File

@ -1,374 +0,0 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: broker/service/proto/broker.proto
package go_micro_broker
import (
context "context"
fmt "fmt"
proto "github.com/golang/protobuf/proto"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
math "math"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
type Empty struct {
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Empty) Reset() { *m = Empty{} }
func (m *Empty) String() string { return proto.CompactTextString(m) }
func (*Empty) ProtoMessage() {}
func (*Empty) Descriptor() ([]byte, []int) {
return fileDescriptor_df4d8f04292cf3fe, []int{0}
}
func (m *Empty) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Empty.Unmarshal(m, b)
}
func (m *Empty) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Empty.Marshal(b, m, deterministic)
}
func (m *Empty) XXX_Merge(src proto.Message) {
xxx_messageInfo_Empty.Merge(m, src)
}
func (m *Empty) XXX_Size() int {
return xxx_messageInfo_Empty.Size(m)
}
func (m *Empty) XXX_DiscardUnknown() {
xxx_messageInfo_Empty.DiscardUnknown(m)
}
var xxx_messageInfo_Empty proto.InternalMessageInfo
type PublishRequest struct {
Topic string `protobuf:"bytes,1,opt,name=topic,proto3" json:"topic,omitempty"`
Message *Message `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *PublishRequest) Reset() { *m = PublishRequest{} }
func (m *PublishRequest) String() string { return proto.CompactTextString(m) }
func (*PublishRequest) ProtoMessage() {}
func (*PublishRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_df4d8f04292cf3fe, []int{1}
}
func (m *PublishRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_PublishRequest.Unmarshal(m, b)
}
func (m *PublishRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_PublishRequest.Marshal(b, m, deterministic)
}
func (m *PublishRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_PublishRequest.Merge(m, src)
}
func (m *PublishRequest) XXX_Size() int {
return xxx_messageInfo_PublishRequest.Size(m)
}
func (m *PublishRequest) XXX_DiscardUnknown() {
xxx_messageInfo_PublishRequest.DiscardUnknown(m)
}
var xxx_messageInfo_PublishRequest proto.InternalMessageInfo
func (m *PublishRequest) GetTopic() string {
if m != nil {
return m.Topic
}
return ""
}
func (m *PublishRequest) GetMessage() *Message {
if m != nil {
return m.Message
}
return nil
}
type SubscribeRequest struct {
Topic string `protobuf:"bytes,1,opt,name=topic,proto3" json:"topic,omitempty"`
Queue string `protobuf:"bytes,2,opt,name=queue,proto3" json:"queue,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *SubscribeRequest) Reset() { *m = SubscribeRequest{} }
func (m *SubscribeRequest) String() string { return proto.CompactTextString(m) }
func (*SubscribeRequest) ProtoMessage() {}
func (*SubscribeRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_df4d8f04292cf3fe, []int{2}
}
func (m *SubscribeRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_SubscribeRequest.Unmarshal(m, b)
}
func (m *SubscribeRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_SubscribeRequest.Marshal(b, m, deterministic)
}
func (m *SubscribeRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_SubscribeRequest.Merge(m, src)
}
func (m *SubscribeRequest) XXX_Size() int {
return xxx_messageInfo_SubscribeRequest.Size(m)
}
func (m *SubscribeRequest) XXX_DiscardUnknown() {
xxx_messageInfo_SubscribeRequest.DiscardUnknown(m)
}
var xxx_messageInfo_SubscribeRequest proto.InternalMessageInfo
func (m *SubscribeRequest) GetTopic() string {
if m != nil {
return m.Topic
}
return ""
}
func (m *SubscribeRequest) GetQueue() string {
if m != nil {
return m.Queue
}
return ""
}
type Message struct {
Header map[string]string `protobuf:"bytes,1,rep,name=header,proto3" json:"header,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
Body []byte `protobuf:"bytes,2,opt,name=body,proto3" json:"body,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Message) Reset() { *m = Message{} }
func (m *Message) String() string { return proto.CompactTextString(m) }
func (*Message) ProtoMessage() {}
func (*Message) Descriptor() ([]byte, []int) {
return fileDescriptor_df4d8f04292cf3fe, []int{3}
}
func (m *Message) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Message.Unmarshal(m, b)
}
func (m *Message) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Message.Marshal(b, m, deterministic)
}
func (m *Message) XXX_Merge(src proto.Message) {
xxx_messageInfo_Message.Merge(m, src)
}
func (m *Message) XXX_Size() int {
return xxx_messageInfo_Message.Size(m)
}
func (m *Message) XXX_DiscardUnknown() {
xxx_messageInfo_Message.DiscardUnknown(m)
}
var xxx_messageInfo_Message proto.InternalMessageInfo
func (m *Message) GetHeader() map[string]string {
if m != nil {
return m.Header
}
return nil
}
func (m *Message) GetBody() []byte {
if m != nil {
return m.Body
}
return nil
}
func init() {
proto.RegisterType((*Empty)(nil), "go.micro.broker.Empty")
proto.RegisterType((*PublishRequest)(nil), "go.micro.broker.PublishRequest")
proto.RegisterType((*SubscribeRequest)(nil), "go.micro.broker.SubscribeRequest")
proto.RegisterType((*Message)(nil), "go.micro.broker.Message")
proto.RegisterMapType((map[string]string)(nil), "go.micro.broker.Message.HeaderEntry")
}
func init() { proto.RegisterFile("broker/service/proto/broker.proto", fileDescriptor_df4d8f04292cf3fe) }
var fileDescriptor_df4d8f04292cf3fe = []byte{
// 299 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x51, 0x4d, 0x4b, 0xc3, 0x40,
0x14, 0xec, 0xb6, 0xb6, 0xa1, 0xaf, 0xa2, 0x65, 0x29, 0x12, 0x7a, 0x31, 0x0d, 0x1e, 0x72, 0xda,
0x48, 0xbc, 0xa8, 0x88, 0x07, 0xb1, 0xe0, 0x41, 0x41, 0xd6, 0x9b, 0xb7, 0x6c, 0xfa, 0x68, 0x43,
0x1b, 0x37, 0xdd, 0x4d, 0x0a, 0xf9, 0x23, 0x9e, 0xfc, 0xb1, 0xd2, 0xdd, 0xf8, 0xd5, 0x50, 0x6f,
0x6f, 0xde, 0xce, 0xce, 0x1b, 0x66, 0x60, 0x22, 0x94, 0x5c, 0xa2, 0x0a, 0x35, 0xaa, 0x4d, 0x9a,
0x60, 0x98, 0x2b, 0x59, 0xc8, 0xd0, 0x2e, 0x99, 0x01, 0xf4, 0x78, 0x2e, 0x59, 0x96, 0x26, 0x4a,
0x32, 0xbb, 0xf6, 0x1d, 0xe8, 0x4e, 0xb3, 0xbc, 0xa8, 0xfc, 0x57, 0x38, 0x7a, 0x2e, 0xc5, 0x2a,
0xd5, 0x0b, 0x8e, 0xeb, 0x12, 0x75, 0x41, 0x47, 0xd0, 0x2d, 0x64, 0x9e, 0x26, 0x2e, 0xf1, 0x48,
0xd0, 0xe7, 0x16, 0xd0, 0x08, 0x9c, 0x0c, 0xb5, 0x8e, 0xe7, 0xe8, 0xb6, 0x3d, 0x12, 0x0c, 0x22,
0x97, 0xed, 0x68, 0xb2, 0x27, 0xfb, 0xce, 0xbf, 0x88, 0xfe, 0x2d, 0x0c, 0x5f, 0x4a, 0xa1, 0x13,
0x95, 0x0a, 0xfc, 0x5f, 0x7d, 0x04, 0xdd, 0x75, 0x89, 0xa5, 0xd5, 0xee, 0x73, 0x0b, 0xfc, 0x77,
0x02, 0x4e, 0x2d, 0x4a, 0x6f, 0xa0, 0xb7, 0xc0, 0x78, 0x86, 0xca, 0x25, 0x5e, 0x27, 0x18, 0x44,
0x67, 0xfb, 0xce, 0xb3, 0x07, 0x43, 0x9b, 0xbe, 0x15, 0xaa, 0xe2, 0xf5, 0x1f, 0x4a, 0xe1, 0x40,
0xc8, 0x59, 0x65, 0xe4, 0x0f, 0xb9, 0x99, 0xc7, 0x57, 0x30, 0xf8, 0x45, 0xa5, 0x43, 0xe8, 0x2c,
0xb1, 0xaa, 0x6d, 0x6d, 0xc7, 0xad, 0xa9, 0x4d, 0xbc, 0xfa, 0x31, 0x65, 0xc0, 0x75, 0xfb, 0x92,
0x44, 0x1f, 0x04, 0x7a, 0x77, 0xe6, 0x2a, 0xbd, 0x07, 0xa7, 0xce, 0x8f, 0x9e, 0x36, 0x2c, 0xfd,
0x4d, 0x76, 0x7c, 0xd2, 0x20, 0xd8, 0x0e, 0x5a, 0xf4, 0x11, 0xfa, 0xdf, 0x49, 0xd1, 0x49, 0x83,
0xb6, 0x9b, 0xe2, 0x78, 0x6f, 0xf8, 0x7e, 0xeb, 0x9c, 0x88, 0x9e, 0x29, 0xfd, 0xe2, 0x33, 0x00,
0x00, 0xff, 0xff, 0x19, 0x9f, 0x10, 0x75, 0x19, 0x02, 0x00, 0x00,
}
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConn
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion4
// BrokerClient is the client API for Broker service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type BrokerClient interface {
Publish(ctx context.Context, in *PublishRequest, opts ...grpc.CallOption) (*Empty, error)
Subscribe(ctx context.Context, in *SubscribeRequest, opts ...grpc.CallOption) (Broker_SubscribeClient, error)
}
type brokerClient struct {
cc *grpc.ClientConn
}
func NewBrokerClient(cc *grpc.ClientConn) BrokerClient {
return &brokerClient{cc}
}
func (c *brokerClient) Publish(ctx context.Context, in *PublishRequest, opts ...grpc.CallOption) (*Empty, error) {
out := new(Empty)
err := c.cc.Invoke(ctx, "/go.micro.broker.Broker/Publish", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *brokerClient) Subscribe(ctx context.Context, in *SubscribeRequest, opts ...grpc.CallOption) (Broker_SubscribeClient, error) {
stream, err := c.cc.NewStream(ctx, &_Broker_serviceDesc.Streams[0], "/go.micro.broker.Broker/Subscribe", opts...)
if err != nil {
return nil, err
}
x := &brokerSubscribeClient{stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
type Broker_SubscribeClient interface {
Recv() (*Message, error)
grpc.ClientStream
}
type brokerSubscribeClient struct {
grpc.ClientStream
}
func (x *brokerSubscribeClient) Recv() (*Message, error) {
m := new(Message)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
// BrokerServer is the server API for Broker service.
type BrokerServer interface {
Publish(context.Context, *PublishRequest) (*Empty, error)
Subscribe(*SubscribeRequest, Broker_SubscribeServer) error
}
// UnimplementedBrokerServer can be embedded to have forward compatible implementations.
type UnimplementedBrokerServer struct {
}
func (*UnimplementedBrokerServer) Publish(ctx context.Context, req *PublishRequest) (*Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method Publish not implemented")
}
func (*UnimplementedBrokerServer) Subscribe(req *SubscribeRequest, srv Broker_SubscribeServer) error {
return status.Errorf(codes.Unimplemented, "method Subscribe not implemented")
}
func RegisterBrokerServer(s *grpc.Server, srv BrokerServer) {
s.RegisterService(&_Broker_serviceDesc, srv)
}
func _Broker_Publish_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(PublishRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(BrokerServer).Publish(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/go.micro.broker.Broker/Publish",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(BrokerServer).Publish(ctx, req.(*PublishRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Broker_Subscribe_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(SubscribeRequest)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(BrokerServer).Subscribe(m, &brokerSubscribeServer{stream})
}
type Broker_SubscribeServer interface {
Send(*Message) error
grpc.ServerStream
}
type brokerSubscribeServer struct {
grpc.ServerStream
}
func (x *brokerSubscribeServer) Send(m *Message) error {
return x.ServerStream.SendMsg(m)
}
var _Broker_serviceDesc = grpc.ServiceDesc{
ServiceName: "go.micro.broker.Broker",
HandlerType: (*BrokerServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "Publish",
Handler: _Broker_Publish_Handler,
},
},
Streams: []grpc.StreamDesc{
{
StreamName: "Subscribe",
Handler: _Broker_Subscribe_Handler,
ServerStreams: true,
},
},
Metadata: "broker/service/proto/broker.proto",
}

View File

@ -1,185 +0,0 @@
// Code generated by protoc-gen-micro. DO NOT EDIT.
// source: broker/service/proto/broker.proto
package go_micro_broker
import (
fmt "fmt"
proto "github.com/golang/protobuf/proto"
math "math"
)
import (
context "context"
api "github.com/micro/go-micro/v2/api"
client "github.com/micro/go-micro/v2/client"
server "github.com/micro/go-micro/v2/server"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
// Reference imports to suppress errors if they are not otherwise used.
var _ api.Endpoint
var _ context.Context
var _ client.Option
var _ server.Option
// Api Endpoints for Broker service
func NewBrokerEndpoints() []*api.Endpoint {
return []*api.Endpoint{}
}
// Client API for Broker service
type BrokerService interface {
Publish(ctx context.Context, in *PublishRequest, opts ...client.CallOption) (*Empty, error)
Subscribe(ctx context.Context, in *SubscribeRequest, opts ...client.CallOption) (Broker_SubscribeService, error)
}
type brokerService struct {
c client.Client
name string
}
func NewBrokerService(name string, c client.Client) BrokerService {
return &brokerService{
c: c,
name: name,
}
}
func (c *brokerService) Publish(ctx context.Context, in *PublishRequest, opts ...client.CallOption) (*Empty, error) {
req := c.c.NewRequest(c.name, "Broker.Publish", in)
out := new(Empty)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *brokerService) Subscribe(ctx context.Context, in *SubscribeRequest, opts ...client.CallOption) (Broker_SubscribeService, error) {
req := c.c.NewRequest(c.name, "Broker.Subscribe", &SubscribeRequest{})
stream, err := c.c.Stream(ctx, req, opts...)
if err != nil {
return nil, err
}
if err := stream.Send(in); err != nil {
return nil, err
}
return &brokerServiceSubscribe{stream}, nil
}
type Broker_SubscribeService interface {
Context() context.Context
SendMsg(interface{}) error
RecvMsg(interface{}) error
Close() error
Recv() (*Message, error)
}
type brokerServiceSubscribe struct {
stream client.Stream
}
func (x *brokerServiceSubscribe) Close() error {
return x.stream.Close()
}
func (x *brokerServiceSubscribe) Context() context.Context {
return x.stream.Context()
}
func (x *brokerServiceSubscribe) SendMsg(m interface{}) error {
return x.stream.Send(m)
}
func (x *brokerServiceSubscribe) RecvMsg(m interface{}) error {
return x.stream.Recv(m)
}
func (x *brokerServiceSubscribe) Recv() (*Message, error) {
m := new(Message)
err := x.stream.Recv(m)
if err != nil {
return nil, err
}
return m, nil
}
// Server API for Broker service
type BrokerHandler interface {
Publish(context.Context, *PublishRequest, *Empty) error
Subscribe(context.Context, *SubscribeRequest, Broker_SubscribeStream) error
}
func RegisterBrokerHandler(s server.Server, hdlr BrokerHandler, opts ...server.HandlerOption) error {
type broker interface {
Publish(ctx context.Context, in *PublishRequest, out *Empty) error
Subscribe(ctx context.Context, stream server.Stream) error
}
type Broker struct {
broker
}
h := &brokerHandler{hdlr}
return s.Handle(s.NewHandler(&Broker{h}, opts...))
}
type brokerHandler struct {
BrokerHandler
}
func (h *brokerHandler) Publish(ctx context.Context, in *PublishRequest, out *Empty) error {
return h.BrokerHandler.Publish(ctx, in, out)
}
func (h *brokerHandler) Subscribe(ctx context.Context, stream server.Stream) error {
m := new(SubscribeRequest)
if err := stream.Recv(m); err != nil {
return err
}
return h.BrokerHandler.Subscribe(ctx, m, &brokerSubscribeStream{stream})
}
type Broker_SubscribeStream interface {
Context() context.Context
SendMsg(interface{}) error
RecvMsg(interface{}) error
Close() error
Send(*Message) error
}
type brokerSubscribeStream struct {
stream server.Stream
}
func (x *brokerSubscribeStream) Close() error {
return x.stream.Close()
}
func (x *brokerSubscribeStream) Context() context.Context {
return x.stream.Context()
}
func (x *brokerSubscribeStream) SendMsg(m interface{}) error {
return x.stream.Send(m)
}
func (x *brokerSubscribeStream) RecvMsg(m interface{}) error {
return x.stream.Recv(m)
}
func (x *brokerSubscribeStream) Send(m *Message) error {
return x.stream.Send(m)
}

View File

@ -1,25 +0,0 @@
syntax = "proto3";
package go.micro.broker;
service Broker {
rpc Publish(PublishRequest) returns (Empty) {};
rpc Subscribe(SubscribeRequest) returns (stream Message) {};
}
message Empty {}
message PublishRequest {
string topic = 1;
Message message = 2;
}
message SubscribeRequest {
string topic = 1;
string queue = 2;
}
message Message {
map<string,string> header = 1;
bytes body = 2;
}

View File

@ -1,152 +0,0 @@
// Package service provides the broker service client
package service
import (
"context"
"time"
"github.com/micro/go-micro/v2/broker"
pb "github.com/micro/go-micro/v2/broker/service/proto"
"github.com/micro/go-micro/v2/client"
"github.com/micro/go-micro/v2/logger"
)
type serviceBroker struct {
Addrs []string
Client pb.BrokerService
options broker.Options
}
var (
DefaultName = "go.micro.broker"
)
func (b *serviceBroker) Address() string {
return b.Addrs[0]
}
func (b *serviceBroker) Connect() error {
return nil
}
func (b *serviceBroker) Disconnect() error {
return nil
}
func (b *serviceBroker) Init(opts ...broker.Option) error {
for _, o := range opts {
o(&b.options)
}
return nil
}
func (b *serviceBroker) Options() broker.Options {
return b.options
}
func (b *serviceBroker) Publish(topic string, msg *broker.Message, opts ...broker.PublishOption) error {
if logger.V(logger.DebugLevel, logger.DefaultLogger) {
logger.Debugf("Publishing to topic %s broker %v", topic, b.Addrs)
}
_, err := b.Client.Publish(context.TODO(), &pb.PublishRequest{
Topic: topic,
Message: &pb.Message{
Header: msg.Header,
Body: msg.Body,
},
}, client.WithAddress(b.Addrs...))
return err
}
func (b *serviceBroker) Subscribe(topic string, handler broker.Handler, opts ...broker.SubscribeOption) (broker.Subscriber, error) {
var options broker.SubscribeOptions
for _, o := range opts {
o(&options)
}
if logger.V(logger.DebugLevel, logger.DefaultLogger) {
logger.Debugf("Subscribing to topic %s queue %s broker %v", topic, options.Queue, b.Addrs)
}
stream, err := b.Client.Subscribe(context.TODO(), &pb.SubscribeRequest{
Topic: topic,
Queue: options.Queue,
}, client.WithAddress(b.Addrs...), client.WithRequestTimeout(time.Hour))
if err != nil {
return nil, err
}
sub := &serviceSub{
topic: topic,
queue: options.Queue,
handler: handler,
stream: stream,
closed: make(chan bool),
options: options,
}
go func() {
for {
select {
case <-sub.closed:
if logger.V(logger.DebugLevel, logger.DefaultLogger) {
logger.Debugf("Unsubscribed from topic %s", topic)
}
return
default:
if logger.V(logger.DebugLevel, logger.DefaultLogger) {
// run the subscriber
logger.Debugf("Streaming from broker %v to topic [%s] queue [%s]", b.Addrs, topic, options.Queue)
}
if err := sub.run(); err != nil {
if logger.V(logger.DebugLevel, logger.DefaultLogger) {
logger.Debugf("Resubscribing to topic %s broker %v", topic, b.Addrs)
}
stream, err := b.Client.Subscribe(context.TODO(), &pb.SubscribeRequest{
Topic: topic,
Queue: options.Queue,
}, client.WithAddress(b.Addrs...), client.WithRequestTimeout(time.Hour))
if err != nil {
if logger.V(logger.DebugLevel, logger.DefaultLogger) {
logger.Debugf("Failed to resubscribe to topic %s: %v", topic, err)
}
time.Sleep(time.Second)
continue
}
// new stream
sub.stream = stream
}
}
}
}()
return sub, nil
}
func (b *serviceBroker) String() string {
return "service"
}
func NewBroker(opts ...broker.Option) broker.Broker {
var options broker.Options
for _, o := range opts {
o(&options)
}
addrs := options.Addrs
if len(addrs) == 0 {
addrs = []string{"127.0.0.1:8001"}
}
cli := client.DefaultClient
// get options client from the context. We set this in the context to prevent an import loop, as
// the client depends on the broker
if c, ok := options.Context.Value(clientKey{}).(client.Client); ok {
cli = c
}
return &serviceBroker{
Addrs: addrs,
Client: pb.NewBrokerService(DefaultName, cli),
options: options,
}
}

View File

@ -1,108 +0,0 @@
package service
import (
"github.com/micro/go-micro/v2/broker"
pb "github.com/micro/go-micro/v2/broker/service/proto"
"github.com/micro/go-micro/v2/logger"
)
type serviceSub struct {
topic string
queue string
handler broker.Handler
stream pb.Broker_SubscribeService
closed chan bool
options broker.SubscribeOptions
}
type serviceEvent struct {
topic string
err error
message *broker.Message
}
func (s *serviceEvent) Topic() string {
return s.topic
}
func (s *serviceEvent) Message() *broker.Message {
return s.message
}
func (s *serviceEvent) Ack() error {
return nil
}
func (s *serviceEvent) Error() error {
return s.err
}
func (s *serviceSub) isClosed() bool {
select {
case <-s.closed:
return true
default:
return false
}
}
func (s *serviceSub) run() error {
exit := make(chan bool)
go func() {
select {
case <-exit:
case <-s.closed:
}
// close the stream
s.stream.Close()
}()
for {
// TODO: do not fail silently
msg, err := s.stream.Recv()
if err != nil {
if logger.V(logger.DebugLevel, logger.DefaultLogger) {
logger.Debugf("Streaming error for subcription to topic %s: %v", s.Topic(), err)
}
// close the exit channel
close(exit)
// don't return an error if we unsubscribed
if s.isClosed() {
return nil
}
// return stream error
return err
}
p := &serviceEvent{
topic: s.topic,
message: &broker.Message{
Header: msg.Header,
Body: msg.Body,
},
}
p.err = s.handler(p)
}
}
func (s *serviceSub) Options() broker.SubscribeOptions {
return s.options
}
func (s *serviceSub) Topic() string {
return s.topic
}
func (s *serviceSub) Unsubscribe() error {
select {
case <-s.closed:
return nil
default:
close(s.closed)
}
return nil
}

View File

@ -5,7 +5,7 @@ import (
"encoding/json" "encoding/json"
"github.com/bradfitz/gomemcache/memcache" "github.com/bradfitz/gomemcache/memcache"
"github.com/micro/go-micro/v2/cache" "github.com/micro/go-micro/v3/cache"
) )
type memcacheCache struct { type memcacheCache struct {

View File

@ -4,8 +4,8 @@ package memory
import ( import (
"sync" "sync"
"github.com/micro/go-micro/v2/cache" "github.com/micro/go-micro/v3/cache"
"github.com/micro/go-micro/v2/errors" "github.com/micro/go-micro/v3/errors"
) )
type memoryCache struct { type memoryCache struct {

View File

@ -4,7 +4,7 @@ import (
"context" "context"
"time" "time"
"github.com/micro/go-micro/v2/util/backoff" "github.com/micro/go-micro/v3/util/backoff"
) )
type BackoffFunc func(ctx context.Context, req Request, attempts int) (time.Duration, error) type BackoffFunc func(ctx context.Context, req Request, attempts int) (time.Duration, error)

View File

@ -16,10 +16,13 @@ func TestBackoff(t *testing.T) {
7900 * time.Millisecond, 7900 * time.Millisecond,
} }
c := NewClient() r := &testRequest{
service: "test",
method: "test",
}
for i := 0; i < 5; i++ { for i := 0; i < 5; i++ {
d, err := exponentialBackoff(context.TODO(), c.NewRequest("test", "test", nil), i) d, err := exponentialBackoff(context.TODO(), r, i)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@ -7,7 +7,7 @@ import (
"hash/fnv" "hash/fnv"
"time" "time"
"github.com/micro/go-micro/v2/metadata" "github.com/micro/go-micro/v3/metadata"
cache "github.com/patrickmn/go-cache" cache "github.com/patrickmn/go-cache"
) )
@ -24,12 +24,12 @@ type Cache struct {
} }
// Get a response from the cache // Get a response from the cache
func (c *Cache) Get(ctx context.Context, req *Request) (interface{}, bool) { func (c *Cache) Get(ctx context.Context, req Request) (interface{}, bool) {
return c.cache.Get(key(ctx, req)) 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) { func (c *Cache) Set(ctx context.Context, req Request, rsp interface{}, expiry time.Duration) {
c.cache.Set(key(ctx, req), rsp, expiry) c.cache.Set(key(ctx, req), rsp, expiry)
} }
@ -47,16 +47,16 @@ func (c *Cache) List() map[string]string {
} }
// 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 { func key(ctx context.Context, req Request) string {
ns, _ := metadata.Get(ctx, "Micro-Namespace") ns, _ := metadata.Get(ctx, "Micro-Namespace")
bytes, _ := json.Marshal(map[string]interface{}{ bytes, _ := json.Marshal(map[string]interface{}{
"namespace": ns, "namespace": ns,
"request": map[string]interface{}{ "request": map[string]interface{}{
"service": (*req).Service(), "service": req.Service(),
"endpoint": (*req).Endpoint(), "endpoint": req.Endpoint(),
"method": (*req).Method(), "method": req.Method(),
"body": (*req).Body(), "body": req.Body(),
}, },
}) })

View File

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

View File

@ -5,7 +5,7 @@ import (
"context" "context"
"time" "time"
"github.com/micro/go-micro/v2/codec" "github.com/micro/go-micro/v3/codec"
) )
// Client is the interface used to make requests to services. // Client is the interface used to make requests to services.
@ -91,8 +91,6 @@ type MessageOption func(*MessageOptions)
type RequestOption func(*RequestOptions) type RequestOption func(*RequestOptions)
var ( var (
// DefaultClient is a default client to use out of the box
DefaultClient Client = newRpcClient()
// DefaultBackoff is the default backoff function for retries // DefaultBackoff is the default backoff function for retries
DefaultBackoff = exponentialBackoff DefaultBackoff = exponentialBackoff
// DefaultRetry is the default check-for-retry function for retries // DefaultRetry is the default check-for-retry function for retries
@ -105,39 +103,4 @@ var (
DefaultPoolSize = 100 DefaultPoolSize = 100
// DefaultPoolTTL sets the connection pool ttl // DefaultPoolTTL sets the connection pool ttl
DefaultPoolTTL = time.Minute DefaultPoolTTL = time.Minute
// NewClient returns a new client
NewClient func(...Option) Client = newRpcClient
) )
// 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...)
}
// Publishes a publication using the default client. Using the underlying broker
// set within the options.
func Publish(ctx context.Context, msg Message, opts ...PublishOption) error {
return DefaultClient.Publish(ctx, msg, opts...)
}
// 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
func NewRequest(service, endpoint string, request interface{}, reqOpts ...RequestOption) Request {
return DefaultClient.NewRequest(service, endpoint, request, reqOpts...)
}
// Creates a streaming connection with a service and returns responses on the
// channel passed in. It's up to the user to close the streamer.
func NewStream(ctx context.Context, request Request, opts ...CallOption) (Stream, error) {
return DefaultClient.Stream(ctx, request, opts...)
}
func String() string {
return DefaultClient.String()
}

View File

@ -9,8 +9,8 @@ import (
"github.com/golang/protobuf/jsonpb" "github.com/golang/protobuf/jsonpb"
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
"github.com/micro/go-micro/v2/codec" "github.com/micro/go-micro/v3/codec"
"github.com/micro/go-micro/v2/codec/bytes" "github.com/micro/go-micro/v3/codec/bytes"
"github.com/oxtoacart/bpool" "github.com/oxtoacart/bpool"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/encoding" "google.golang.org/grpc/encoding"

View File

@ -1,7 +1,7 @@
package grpc package grpc
import ( import (
"github.com/micro/go-micro/v2/errors" "github.com/micro/go-micro/v3/errors"
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
) )

View File

@ -11,12 +11,12 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/micro/go-micro/v2/broker" "github.com/micro/go-micro/v3/broker"
"github.com/micro/go-micro/v2/client" "github.com/micro/go-micro/v3/client"
raw "github.com/micro/go-micro/v2/codec/bytes" raw "github.com/micro/go-micro/v3/codec/bytes"
"github.com/micro/go-micro/v2/errors" "github.com/micro/go-micro/v3/errors"
"github.com/micro/go-micro/v2/metadata" "github.com/micro/go-micro/v3/metadata"
"github.com/micro/go-micro/v2/registry" "github.com/micro/go-micro/v3/registry"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials"

View File

@ -5,11 +5,12 @@ import (
"net" "net"
"testing" "testing"
"github.com/micro/go-micro/v2/client" "github.com/micro/go-micro/v3/client"
"github.com/micro/go-micro/v2/errors" "github.com/micro/go-micro/v3/errors"
"github.com/micro/go-micro/v2/registry" "github.com/micro/go-micro/v3/registry"
"github.com/micro/go-micro/v2/registry/memory" "github.com/micro/go-micro/v3/registry/memory"
"github.com/micro/go-micro/v2/router" "github.com/micro/go-micro/v3/router"
regRouter "github.com/micro/go-micro/v3/router/registry"
pgrpc "google.golang.org/grpc" pgrpc "google.golang.org/grpc"
pb "google.golang.org/grpc/examples/helloworld/helloworld" pb "google.golang.org/grpc/examples/helloworld/helloworld"
) )
@ -57,7 +58,7 @@ func TestGRPCClient(t *testing.T) {
}) })
// create router // create router
rtr := router.NewRouter(router.Registry(r)) rtr := regRouter.NewRouter(router.Registry(r))
// create client // create client
c := NewClient(client.Router(rtr)) c := NewClient(client.Router(rtr))

View File

@ -1,7 +1,7 @@
package grpc package grpc
import ( import (
"github.com/micro/go-micro/v2/client" "github.com/micro/go-micro/v3/client"
) )
type grpcEvent struct { type grpcEvent struct {

View File

@ -5,7 +5,7 @@ import (
"context" "context"
"crypto/tls" "crypto/tls"
"github.com/micro/go-micro/v2/client" "github.com/micro/go-micro/v3/client"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/encoding" "google.golang.org/grpc/encoding"
) )

View File

@ -4,8 +4,8 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/micro/go-micro/v2/client" "github.com/micro/go-micro/v3/client"
"github.com/micro/go-micro/v2/codec" "github.com/micro/go-micro/v3/codec"
) )
type grpcRequest struct { type grpcRequest struct {

View File

@ -3,8 +3,8 @@ package grpc
import ( import (
"strings" "strings"
"github.com/micro/go-micro/v2/codec" "github.com/micro/go-micro/v3/codec"
"github.com/micro/go-micro/v2/codec/bytes" "github.com/micro/go-micro/v3/codec/bytes"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/encoding" "google.golang.org/grpc/encoding"
) )

View File

@ -5,7 +5,7 @@ import (
"io" "io"
"sync" "sync"
"github.com/micro/go-micro/v2/client" "github.com/micro/go-micro/v3/client"
"google.golang.org/grpc" "google.golang.org/grpc"
) )

View File

@ -1,7 +1,7 @@
package client package mucp
import ( import (
"github.com/micro/go-micro/v2/registry" "github.com/micro/go-micro/v3/registry"
) )
var ( var (

View File

@ -2,10 +2,623 @@
package mucp package mucp
import ( import (
"github.com/micro/go-micro/v2/client" "context"
"fmt"
"sync/atomic"
"time"
"github.com/google/uuid"
"github.com/micro/go-micro/v3/broker"
"github.com/micro/go-micro/v3/client"
"github.com/micro/go-micro/v3/codec"
raw "github.com/micro/go-micro/v3/codec/bytes"
"github.com/micro/go-micro/v3/errors"
"github.com/micro/go-micro/v3/metadata"
"github.com/micro/go-micro/v3/registry"
"github.com/micro/go-micro/v3/transport"
"github.com/micro/go-micro/v3/util/buf"
"github.com/micro/go-micro/v3/util/net"
"github.com/micro/go-micro/v3/util/pool"
) )
// NewClient returns a new micro client interface // NewClient returns a new micro client interface
func NewClient(opts ...client.Option) client.Client { func NewClient(opts ...client.Option) client.Client {
return client.NewClient(opts...) return newClient(opts...)
}
type rpcClient struct {
once atomic.Value
opts client.Options
pool pool.Pool
seq uint64
}
func newClient(opt ...client.Option) client.Client {
opts := client.NewOptions(opt...)
p := pool.NewPool(
pool.Size(opts.PoolSize),
pool.TTL(opts.PoolTTL),
pool.Transport(opts.Transport),
)
rc := &rpcClient{
opts: opts,
pool: p,
seq: 0,
}
rc.once.Store(false)
c := client.Client(rc)
// wrap in reverse
for i := len(opts.Wrappers); i > 0; i-- {
c = opts.Wrappers[i-1](c)
}
return c
}
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 client.Request, resp interface{}, opts client.CallOptions) error {
msg := &transport.Message{
Header: make(map[string]string),
}
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" {
continue
}
msg.Header[k] = v
}
}
// set timeout in nanoseconds
msg.Header["Timeout"] = fmt.Sprintf("%d", opts.RequestTimeout)
// 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)
// no codec specified
if cf == nil {
var err error
cf, err = r.newCodec(req.ContentType())
if err != nil {
return errors.InternalServerError("go.micro.client", err.Error())
}
}
dOpts := []transport.DialOption{
transport.WithStream(),
}
if opts.DialTimeout >= 0 {
dOpts = append(dOpts, transport.WithTimeout(opts.DialTimeout))
}
c, err := r.pool.Get(node.Address, dOpts...)
if err != nil {
return errors.InternalServerError("go.micro.client", "connection error: %v", err)
}
seq := atomic.AddUint64(&r.seq, 1) - 1
codec := newRpcCodec(msg, c, cf, "")
rsp := &rpcResponse{
socket: c,
codec: codec,
}
stream := &rpcStream{
id: fmt.Sprintf("%v", seq),
context: ctx,
request: req,
response: rsp,
codec: codec,
closed: make(chan bool),
release: func(err error) { r.pool.Release(c, err) },
sendEOS: false,
}
// close the stream on exiting this function
defer stream.Close()
// wait for error response
ch := make(chan error, 1)
go func() {
defer func() {
if r := recover(); r != nil {
ch <- errors.InternalServerError("go.micro.client", "panic recovered: %v", r)
}
}()
// send request
if err := stream.Send(req.Body()); err != nil {
ch <- err
return
}
// recv request
if err := stream.Recv(resp); err != nil {
ch <- err
return
}
// success
ch <- nil
}()
var grr error
select {
case err := <-ch:
return err
case <-ctx.Done():
grr = errors.Timeout("go.micro.client", fmt.Sprintf("%v", ctx.Err()))
}
// set the stream error
if grr != nil {
stream.Lock()
stream.err = grr
stream.Unlock()
return grr
}
return nil
}
func (r *rpcClient) stream(ctx context.Context, node *registry.Node, req client.Request, opts client.CallOptions) (client.Stream, error) {
msg := &transport.Message{
Header: make(map[string]string),
}
md, ok := metadata.FromContext(ctx)
if ok {
for k, v := range md {
msg.Header[k] = v
}
}
// set timeout in nanoseconds
if opts.StreamTimeout > time.Duration(0) {
msg.Header["Timeout"] = fmt.Sprintf("%d", opts.StreamTimeout)
}
// set the content type for the request
msg.Header["Content-Type"] = req.ContentType()
// set the accept header
msg.Header["Accept"] = req.ContentType()
// set old codecs
cf := setupProtocol(msg, node)
// no codec specified
if cf == nil {
var err error
cf, err = r.newCodec(req.ContentType())
if err != nil {
return nil, errors.InternalServerError("go.micro.client", err.Error())
}
}
dOpts := []transport.DialOption{
transport.WithStream(),
}
if opts.DialTimeout >= 0 {
dOpts = append(dOpts, transport.WithTimeout(opts.DialTimeout))
}
c, err := r.opts.Transport.Dial(node.Address, dOpts...)
if err != nil {
return nil, errors.InternalServerError("go.micro.client", "connection error: %v", err)
}
// increment the sequence number
seq := atomic.AddUint64(&r.seq, 1) - 1
id := fmt.Sprintf("%v", seq)
// create codec with stream id
codec := newRpcCodec(msg, c, cf, id)
rsp := &rpcResponse{
socket: c,
codec: codec,
}
// set request codec
if r, ok := req.(*rpcRequest); ok {
r.codec = codec
}
stream := &rpcStream{
id: id,
context: ctx,
request: req,
response: rsp,
codec: codec,
// used to close the stream
closed: make(chan bool),
// signal the end of stream,
sendEOS: true,
// release func
release: func(err error) { c.Close() },
}
// wait for error response
ch := make(chan error, 1)
go func() {
// send the first message
ch <- stream.Send(req.Body())
}()
var grr error
select {
case err := <-ch:
grr = err
case <-ctx.Done():
grr = errors.Timeout("go.micro.client", fmt.Sprintf("%v", ctx.Err()))
}
if grr != nil {
// set the error
stream.Lock()
stream.err = grr
stream.Unlock()
// close the stream
stream.Close()
return nil, grr
}
return stream, nil
}
func (r *rpcClient) Init(opts ...client.Option) error {
size := r.opts.PoolSize
ttl := r.opts.PoolTTL
tr := r.opts.Transport
for _, o := range opts {
o(&r.opts)
}
// 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()
// create new pool
r.pool = pool.NewPool(
pool.Size(r.opts.PoolSize),
pool.TTL(r.opts.PoolTTL),
pool.Transport(r.opts.Transport),
)
}
return nil
}
func (r *rpcClient) Options() client.Options {
return r.opts
}
func (r *rpcClient) Call(ctx context.Context, request client.Request, response interface{}, opts ...client.CallOption) error {
// make a copy of call opts
callOpts := r.opts.CallOptions
for _, opt := range opts {
opt(&callOpts)
}
// check if we already have a deadline
if d, ok := ctx.Deadline(); !ok {
// 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
// but we need to set the timeout we pass along
remaining := d.Sub(time.Now())
client.WithRequestTimeout(remaining)(&callOpts)
}
// should we noop right here?
select {
case <-ctx.Done():
return errors.Timeout("go.micro.client", fmt.Sprintf("%v", ctx.Err()))
default:
}
// make copy of call method
rcall := r.call
// wrap the call in reverse
for i := len(callOpts.CallWrappers); i > 0; i-- {
rcall = callOpts.CallWrappers[i-1](rcall)
}
// return errors.New("go.micro.client", "request timeout", 408)
call := func(i int) error {
// 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())
}
// only sleep if greater than 0
if t.Seconds() > 0 {
time.Sleep(t)
}
// use the router passed as a call option, or fallback to the rpc clients router
if callOpts.Router == nil {
callOpts.Router = r.opts.Router
}
// use the selector passed as a call option, or fallback to the rpc clients selector
if callOpts.Selector == nil {
callOpts.Selector = r.opts.Selector
}
// lookup the route to send the request via
route, err := client.LookupRoute(request, callOpts)
if err != nil {
return err
}
// pass a node to enable backwards comparability as changing the
// call func would be a breaking change.
// todo v3: change the call func to accept a route
node := &registry.Node{Address: route.Address, Metadata: route.Metadata}
// make the call
err = rcall(ctx, node, request, response, callOpts)
// record the result of the call to inform future routing decisions
r.opts.Selector.Record(*route, err)
return err
}
// get the retries
retries := callOpts.Retries
// disable retries when using a proxy
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++ {
go func(i int) {
ch <- call(i)
}(i)
select {
case <-ctx.Done():
return errors.Timeout("go.micro.client", fmt.Sprintf("call timeout: %v", ctx.Err()))
case err := <-ch:
// if the call succeeded lets bail early
if err == nil {
return nil
}
retry, rerr := callOpts.Retry(ctx, request, i, err)
if rerr != nil {
return rerr
}
if !retry {
return err
}
gerr = err
}
}
return gerr
}
func (r *rpcClient) Stream(ctx context.Context, request client.Request, opts ...client.CallOption) (client.Stream, error) {
// make a copy of call opts
callOpts := r.opts.CallOptions
for _, opt := range opts {
opt(&callOpts)
}
// should we noop right here?
select {
case <-ctx.Done():
return nil, errors.Timeout("go.micro.client", fmt.Sprintf("%v", ctx.Err()))
default:
}
call := func(i int) (client.Stream, error) {
// 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())
}
// only sleep if greater than 0
if t.Seconds() > 0 {
time.Sleep(t)
}
// use the router passed as a call option, or fallback to the rpc clients router
if callOpts.Router == nil {
callOpts.Router = r.opts.Router
}
// use the selector passed as a call option, or fallback to the rpc clients selector
if callOpts.Selector == nil {
callOpts.Selector = r.opts.Selector
}
// lookup the route to send the request via
route, err := client.LookupRoute(request, callOpts)
if err != nil {
return nil, err
}
// pass a node to enable backwards compatability as changing the
// call func would be a breaking change.
// todo v3: change the call func to accept a route
node := &registry.Node{Address: route.Address, Metadata: route.Metadata}
// perform the call
stream, err := r.stream(ctx, node, request, callOpts)
// record the result of the call to inform future routing decisions
r.opts.Selector.Record(*route, err)
return stream, err
}
type response struct {
stream client.Stream
err error
}
// get the retries
retries := callOpts.Retries
// disable retries when using a proxy
if _, _, ok := net.Proxy(request.Service(), callOpts.Address); ok {
retries = 0
}
ch := make(chan response, retries+1)
var grr error
for i := 0; i <= retries; i++ {
go func(i int) {
s, err := call(i)
ch <- response{s, err}
}(i)
select {
case <-ctx.Done():
return nil, errors.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 {
return rsp.stream, nil
}
retry, rerr := callOpts.Retry(ctx, request, i, rsp.err)
if rerr != nil {
return nil, rerr
}
if !retry {
return nil, rsp.err
}
grr = rsp.err
}
}
return nil, grr
}
func (r *rpcClient) Publish(ctx context.Context, msg client.Message, opts ...client.PublishOption) error {
options := client.PublishOptions{
Context: context.Background(),
}
for _, o := range opts {
o(&options)
}
md, ok := metadata.FromContext(ctx)
if !ok {
md = make(map[string]string)
}
id := uuid.New().String()
md["Content-Type"] = msg.ContentType()
md["Micro-Topic"] = msg.Topic()
md["Micro-Id"] = id
// set the topic
topic := msg.Topic()
// get the exchange
if len(options.Exchange) > 0 {
topic = options.Exchange
}
// encode message body
cf, err := r.newCodec(msg.ContentType())
if err != nil {
return errors.InternalServerError("go.micro.client", err.Error())
}
var body []byte
// passed in raw data
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{
Target: topic,
Type: codec.Event,
Header: map[string]string{
"Micro-Id": id,
"Micro-Topic": msg.Topic(),
},
}, msg.Payload()); err != nil {
return errors.InternalServerError("go.micro.client", err.Error())
}
// set the body
body = b.Bytes()
}
if !r.once.Load().(bool) {
if err = r.opts.Broker.Connect(); err != nil {
return errors.InternalServerError("go.micro.client", err.Error())
}
r.once.Store(true)
}
return r.opts.Broker.Publish(topic, &broker.Message{
Header: md,
Body: body,
}, broker.PublishContext(options.Context))
}
func (r *rpcClient) NewMessage(topic string, message interface{}, opts ...client.MessageOption) client.Message {
return newMessage(topic, message, r.opts.ContentType, opts...)
}
func (r *rpcClient) NewRequest(service, method string, request interface{}, reqOpts ...client.RequestOption) client.Request {
return newRequest(service, method, request, r.opts.ContentType, reqOpts...)
}
func (r *rpcClient) String() string {
return "mucp"
} }

View File

@ -1,19 +1,19 @@
package client package mucp
import ( import (
"bytes" "bytes"
errs "errors" errs "errors"
"github.com/micro/go-micro/v2/codec" "github.com/micro/go-micro/v3/codec"
raw "github.com/micro/go-micro/v2/codec/bytes" raw "github.com/micro/go-micro/v3/codec/bytes"
"github.com/micro/go-micro/v2/codec/grpc" "github.com/micro/go-micro/v3/codec/grpc"
"github.com/micro/go-micro/v2/codec/json" "github.com/micro/go-micro/v3/codec/json"
"github.com/micro/go-micro/v2/codec/jsonrpc" "github.com/micro/go-micro/v3/codec/jsonrpc"
"github.com/micro/go-micro/v2/codec/proto" "github.com/micro/go-micro/v3/codec/proto"
"github.com/micro/go-micro/v2/codec/protorpc" "github.com/micro/go-micro/v3/codec/protorpc"
"github.com/micro/go-micro/v2/errors" "github.com/micro/go-micro/v3/errors"
"github.com/micro/go-micro/v2/registry" "github.com/micro/go-micro/v3/registry"
"github.com/micro/go-micro/v2/transport" "github.com/micro/go-micro/v3/transport"
) )
const ( const (

View File

@ -1,4 +1,8 @@
package client package mucp
import (
"github.com/micro/go-micro/v3/client"
)
type message struct { type message struct {
topic string topic string
@ -6,8 +10,8 @@ type message struct {
payload interface{} payload interface{}
} }
func newMessage(topic string, payload interface{}, contentType string, opts ...MessageOption) Message { func newMessage(topic string, payload interface{}, contentType string, opts ...client.MessageOption) client.Message {
var options MessageOptions var options client.MessageOptions
for _, o := range opts { for _, o := range opts {
o(&options) o(&options)
} }

View File

@ -1,7 +1,8 @@
package client package mucp
import ( import (
"github.com/micro/go-micro/v2/codec" "github.com/micro/go-micro/v3/client"
"github.com/micro/go-micro/v3/codec"
) )
type rpcRequest struct { type rpcRequest struct {
@ -11,11 +12,11 @@ type rpcRequest struct {
contentType string contentType string
codec codec.Codec codec codec.Codec
body interface{} body interface{}
opts RequestOptions opts client.RequestOptions
} }
func newRequest(service, endpoint string, request interface{}, contentType string, reqOpts ...RequestOption) Request { func newRequest(service, endpoint string, request interface{}, contentType string, reqOpts ...client.RequestOption) client.Request {
var opts RequestOptions var opts client.RequestOptions
for _, o := range reqOpts { for _, o := range reqOpts {
o(&opts) o(&opts)

View File

@ -1,7 +1,9 @@
package client package mucp
import ( import (
"testing" "testing"
"github.com/micro/go-micro/v3/client"
) )
func TestRequestOptions(t *testing.T) { func TestRequestOptions(t *testing.T) {
@ -16,7 +18,7 @@ func TestRequestOptions(t *testing.T) {
t.Fatalf("expected 'endpoint' got %s", r.ContentType()) t.Fatalf("expected 'endpoint' got %s", r.ContentType())
} }
r2 := newRequest("service", "endpoint", nil, "application/json", WithContentType("application/protobuf")) r2 := newRequest("service", "endpoint", nil, "application/json", client.WithContentType("application/protobuf"))
if r2.ContentType() != "application/protobuf" { if r2.ContentType() != "application/protobuf" {
t.Fatalf("expected 'endpoint' got %s", r2.ContentType()) t.Fatalf("expected 'endpoint' got %s", r2.ContentType())
} }

View File

@ -1,8 +1,8 @@
package client package mucp
import ( import (
"github.com/micro/go-micro/v2/codec" "github.com/micro/go-micro/v3/codec"
"github.com/micro/go-micro/v2/transport" "github.com/micro/go-micro/v3/transport"
) )
type rpcResponse struct { type rpcResponse struct {

View File

@ -1,11 +1,12 @@
package client package mucp
import ( import (
"context" "context"
"io" "io"
"sync" "sync"
"github.com/micro/go-micro/v2/codec" "github.com/micro/go-micro/v3/client"
"github.com/micro/go-micro/v3/codec"
) )
// Implements the streamer interface // Implements the streamer interface
@ -14,8 +15,8 @@ type rpcStream struct {
id string id string
closed chan bool closed chan bool
err error err error
request Request request client.Request
response Response response client.Response
codec codec.Codec codec codec.Codec
context context.Context context context.Context
@ -39,11 +40,11 @@ func (r *rpcStream) Context() context.Context {
return r.context return r.context
} }
func (r *rpcStream) Request() Request { func (r *rpcStream) Request() client.Request {
return r.request return r.request
} }
func (r *rpcStream) Response() Response { func (r *rpcStream) Response() client.Response {
return r.response return r.response
} }

View File

@ -1,13 +1,14 @@
package client package mucp
import ( import (
"context" "context"
"fmt" "fmt"
"testing" "testing"
"github.com/micro/go-micro/v2/errors" "github.com/micro/go-micro/v3/client"
"github.com/micro/go-micro/v2/registry" "github.com/micro/go-micro/v3/errors"
"github.com/micro/go-micro/v2/registry/memory" "github.com/micro/go-micro/v3/registry"
"github.com/micro/go-micro/v3/registry/memory"
) )
func newTestRegistry() registry.Registry { func newTestRegistry() registry.Registry {
@ -20,8 +21,8 @@ func TestCallAddress(t *testing.T) {
endpoint := "Test.Endpoint" endpoint := "Test.Endpoint"
address := "10.1.10.1:8080" address := "10.1.10.1:8080"
wrap := func(cf CallFunc) CallFunc { wrap := func(cf client.CallFunc) client.CallFunc {
return func(ctx context.Context, node *registry.Node, req Request, rsp interface{}, opts CallOptions) error { return func(ctx context.Context, node *registry.Node, req client.Request, rsp interface{}, opts client.CallOptions) error {
called = true called = true
if req.Service() != service { if req.Service() != service {
@ -43,14 +44,14 @@ func TestCallAddress(t *testing.T) {
r := newTestRegistry() r := newTestRegistry()
c := NewClient( c := NewClient(
Registry(r), client.Registry(r),
WrapCall(wrap), client.WrapCall(wrap),
) )
req := c.NewRequest(service, endpoint, nil) req := c.NewRequest(service, endpoint, nil)
// test calling remote address // test calling remote address
if err := c.Call(context.Background(), req, nil, WithAddress(address)); err != nil { if err := c.Call(context.Background(), req, nil, client.WithAddress(address)); err != nil {
t.Fatal("call with address error", err) t.Fatal("call with address error", err)
} }
@ -67,8 +68,8 @@ func TestCallRetry(t *testing.T) {
var called int var called int
wrap := func(cf CallFunc) CallFunc { wrap := func(cf client.CallFunc) client.CallFunc {
return func(ctx context.Context, node *registry.Node, req Request, rsp interface{}, opts CallOptions) error { return func(ctx context.Context, node *registry.Node, req client.Request, rsp interface{}, opts client.CallOptions) error {
called++ called++
if called == 1 { if called == 1 {
return errors.InternalServerError("test.error", "retry request") return errors.InternalServerError("test.error", "retry request")
@ -81,14 +82,14 @@ func TestCallRetry(t *testing.T) {
r := newTestRegistry() r := newTestRegistry()
c := NewClient( c := NewClient(
Registry(r), client.Registry(r),
WrapCall(wrap), client.WrapCall(wrap),
) )
req := c.NewRequest(service, endpoint, nil) req := c.NewRequest(service, endpoint, nil)
// test calling remote address // test calling remote address
if err := c.Call(context.Background(), req, nil, WithAddress(address)); err != nil { if err := c.Call(context.Background(), req, nil, client.WithAddress(address)); err != nil {
t.Fatal("call with address error", err) t.Fatal("call with address error", err)
} }
@ -105,8 +106,8 @@ func TestCallWrapper(t *testing.T) {
endpoint := "Test.Endpoint" endpoint := "Test.Endpoint"
address := "10.1.10.1:8080" address := "10.1.10.1:8080"
wrap := func(cf CallFunc) CallFunc { wrap := func(cf client.CallFunc) client.CallFunc {
return func(ctx context.Context, node *registry.Node, req Request, rsp interface{}, opts CallOptions) error { return func(ctx context.Context, node *registry.Node, req client.Request, rsp interface{}, opts client.CallOptions) error {
called = true called = true
if req.Service() != service { if req.Service() != service {
@ -128,8 +129,8 @@ func TestCallWrapper(t *testing.T) {
r := newTestRegistry() r := newTestRegistry()
c := NewClient( c := NewClient(
Registry(r), client.Registry(r),
WrapCall(wrap), client.WrapCall(wrap),
) )
r.Register(&registry.Service{ r.Register(&registry.Service{

View File

@ -1,10 +1,11 @@
package client package mucp
import ( import (
"testing" "testing"
"time" "time"
"github.com/micro/go-micro/v2/transport" "github.com/micro/go-micro/v3/client"
"github.com/micro/go-micro/v3/transport"
) )
func TestCallOptions(t *testing.T) { func TestCallOptions(t *testing.T) {
@ -14,33 +15,33 @@ func TestCallOptions(t *testing.T) {
rtimeout time.Duration rtimeout time.Duration
dtimeout time.Duration dtimeout time.Duration
}{ }{
{false, DefaultRetries, DefaultRequestTimeout, transport.DefaultDialTimeout}, {false, client.DefaultRetries, client.DefaultRequestTimeout, transport.DefaultDialTimeout},
{true, 10, time.Second, time.Second * 2}, {true, 10, time.Second, time.Second * 2},
} }
for _, d := range testData { for _, d := range testData {
var opts Options var opts client.Options
var cl Client var cl client.Client
if d.set { if d.set {
opts = NewOptions( opts = client.NewOptions(
Retries(d.retries), client.Retries(d.retries),
RequestTimeout(d.rtimeout), client.RequestTimeout(d.rtimeout),
DialTimeout(d.dtimeout), client.DialTimeout(d.dtimeout),
) )
cl = NewClient( cl = NewClient(
Retries(d.retries), client.Retries(d.retries),
RequestTimeout(d.rtimeout), client.RequestTimeout(d.rtimeout),
DialTimeout(d.dtimeout), client.DialTimeout(d.dtimeout),
) )
} else { } else {
opts = NewOptions() opts = client.NewOptions()
cl = NewClient() cl = NewClient()
} }
// test options and those set in client // test options and those set in client
for _, o := range []Options{opts, cl.Options()} { for _, o := range []client.Options{opts, cl.Options()} {
if o.CallOptions.Retries != d.retries { if o.CallOptions.Retries != d.retries {
t.Fatalf("Expected retries %v got %v", d.retries, o.CallOptions.Retries) t.Fatalf("Expected retries %v got %v", d.retries, o.CallOptions.Retries)
} }
@ -57,12 +58,12 @@ func TestCallOptions(t *testing.T) {
callOpts := o.CallOptions callOpts := o.CallOptions
// create new opts // create new opts
cretries := WithRetries(o.CallOptions.Retries * 10) cretries := client.WithRetries(o.CallOptions.Retries * 10)
crtimeout := WithRequestTimeout(o.CallOptions.RequestTimeout * (time.Second * 10)) crtimeout := client.WithRequestTimeout(o.CallOptions.RequestTimeout * (time.Second * 10))
cdtimeout := WithDialTimeout(o.CallOptions.DialTimeout * (time.Second * 10)) cdtimeout := client.WithDialTimeout(o.CallOptions.DialTimeout * (time.Second * 10))
// set call options // set call options
for _, opt := range []CallOption{cretries, crtimeout, cdtimeout} { for _, opt := range []client.CallOption{cretries, crtimeout, cdtimeout} {
opt(&callOpts) opt(&callOpts)
} }

View File

@ -4,12 +4,14 @@ import (
"context" "context"
"time" "time"
"github.com/micro/go-micro/v2/broker" "github.com/micro/go-micro/v3/broker"
"github.com/micro/go-micro/v2/codec" "github.com/micro/go-micro/v3/broker/http"
"github.com/micro/go-micro/v2/registry" "github.com/micro/go-micro/v3/codec"
"github.com/micro/go-micro/v2/router" "github.com/micro/go-micro/v3/registry"
"github.com/micro/go-micro/v2/selector" "github.com/micro/go-micro/v3/router"
"github.com/micro/go-micro/v2/transport" regRouter "github.com/micro/go-micro/v3/router/registry"
"github.com/micro/go-micro/v3/selector"
"github.com/micro/go-micro/v3/transport"
) )
type Options struct { type Options struct {
@ -102,7 +104,7 @@ func NewOptions(options ...Option) Options {
opts := Options{ opts := Options{
Cache: NewCache(), Cache: NewCache(),
Context: context.Background(), Context: context.Background(),
ContentType: DefaultContentType, ContentType: "application/protobuf",
Codecs: make(map[string]codec.NewCodec), Codecs: make(map[string]codec.NewCodec),
CallOptions: CallOptions{ CallOptions: CallOptions{
Backoff: DefaultBackoff, Backoff: DefaultBackoff,
@ -113,8 +115,8 @@ func NewOptions(options ...Option) Options {
}, },
PoolSize: DefaultPoolSize, PoolSize: DefaultPoolSize,
PoolTTL: DefaultPoolTTL, PoolTTL: DefaultPoolTTL,
Broker: broker.DefaultBroker, Broker: http.NewBroker(),
Router: router.DefaultRouter, Router: regRouter.NewRouter(),
Selector: selector.DefaultSelector, Selector: selector.DefaultSelector,
Transport: transport.DefaultTransport, Transport: transport.DefaultTransport,
} }

View File

@ -3,7 +3,7 @@ package client
import ( import (
"context" "context"
"github.com/micro/go-micro/v2/errors" "github.com/micro/go-micro/v3/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

View File

@ -1,617 +0,0 @@
package client
import (
"context"
"fmt"
"sync/atomic"
"time"
"github.com/google/uuid"
"github.com/micro/go-micro/v2/broker"
"github.com/micro/go-micro/v2/codec"
raw "github.com/micro/go-micro/v2/codec/bytes"
"github.com/micro/go-micro/v2/errors"
"github.com/micro/go-micro/v2/metadata"
"github.com/micro/go-micro/v2/registry"
"github.com/micro/go-micro/v2/transport"
"github.com/micro/go-micro/v2/util/buf"
"github.com/micro/go-micro/v2/util/net"
"github.com/micro/go-micro/v2/util/pool"
)
type rpcClient struct {
once atomic.Value
opts Options
pool pool.Pool
seq uint64
}
func newRpcClient(opt ...Option) Client {
opts := NewOptions(opt...)
p := pool.NewPool(
pool.Size(opts.PoolSize),
pool.TTL(opts.PoolTTL),
pool.Transport(opts.Transport),
)
rc := &rpcClient{
opts: opts,
pool: p,
seq: 0,
}
rc.once.Store(false)
c := Client(rc)
// wrap in reverse
for i := len(opts.Wrappers); i > 0; i-- {
c = opts.Wrappers[i-1](c)
}
return c
}
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 {
msg := &transport.Message{
Header: make(map[string]string),
}
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" {
continue
}
msg.Header[k] = v
}
}
// set timeout in nanoseconds
msg.Header["Timeout"] = fmt.Sprintf("%d", opts.RequestTimeout)
// 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)
// no codec specified
if cf == nil {
var err error
cf, err = r.newCodec(req.ContentType())
if err != nil {
return errors.InternalServerError("go.micro.client", err.Error())
}
}
dOpts := []transport.DialOption{
transport.WithStream(),
}
if opts.DialTimeout >= 0 {
dOpts = append(dOpts, transport.WithTimeout(opts.DialTimeout))
}
c, err := r.pool.Get(node.Address, dOpts...)
if err != nil {
return errors.InternalServerError("go.micro.client", "connection error: %v", err)
}
seq := atomic.AddUint64(&r.seq, 1) - 1
codec := newRpcCodec(msg, c, cf, "")
rsp := &rpcResponse{
socket: c,
codec: codec,
}
stream := &rpcStream{
id: fmt.Sprintf("%v", seq),
context: ctx,
request: req,
response: rsp,
codec: codec,
closed: make(chan bool),
release: func(err error) { r.pool.Release(c, err) },
sendEOS: false,
}
// close the stream on exiting this function
defer stream.Close()
// wait for error response
ch := make(chan error, 1)
go func() {
defer func() {
if r := recover(); r != nil {
ch <- errors.InternalServerError("go.micro.client", "panic recovered: %v", r)
}
}()
// send request
if err := stream.Send(req.Body()); err != nil {
ch <- err
return
}
// recv request
if err := stream.Recv(resp); err != nil {
ch <- err
return
}
// success
ch <- nil
}()
var grr error
select {
case err := <-ch:
return err
case <-ctx.Done():
grr = errors.Timeout("go.micro.client", fmt.Sprintf("%v", ctx.Err()))
}
// set the stream error
if grr != nil {
stream.Lock()
stream.err = grr
stream.Unlock()
return grr
}
return nil
}
func (r *rpcClient) stream(ctx context.Context, node *registry.Node, req Request, opts CallOptions) (Stream, error) {
msg := &transport.Message{
Header: make(map[string]string),
}
md, ok := metadata.FromContext(ctx)
if ok {
for k, v := range md {
msg.Header[k] = v
}
}
// set timeout in nanoseconds
if opts.StreamTimeout > time.Duration(0) {
msg.Header["Timeout"] = fmt.Sprintf("%d", opts.StreamTimeout)
}
// set the content type for the request
msg.Header["Content-Type"] = req.ContentType()
// set the accept header
msg.Header["Accept"] = req.ContentType()
// set old codecs
cf := setupProtocol(msg, node)
// no codec specified
if cf == nil {
var err error
cf, err = r.newCodec(req.ContentType())
if err != nil {
return nil, errors.InternalServerError("go.micro.client", err.Error())
}
}
dOpts := []transport.DialOption{
transport.WithStream(),
}
if opts.DialTimeout >= 0 {
dOpts = append(dOpts, transport.WithTimeout(opts.DialTimeout))
}
c, err := r.opts.Transport.Dial(node.Address, dOpts...)
if err != nil {
return nil, errors.InternalServerError("go.micro.client", "connection error: %v", err)
}
// increment the sequence number
seq := atomic.AddUint64(&r.seq, 1) - 1
id := fmt.Sprintf("%v", seq)
// create codec with stream id
codec := newRpcCodec(msg, c, cf, id)
rsp := &rpcResponse{
socket: c,
codec: codec,
}
// set request codec
if r, ok := req.(*rpcRequest); ok {
r.codec = codec
}
stream := &rpcStream{
id: id,
context: ctx,
request: req,
response: rsp,
codec: codec,
// used to close the stream
closed: make(chan bool),
// signal the end of stream,
sendEOS: true,
// release func
release: func(err error) { c.Close() },
}
// wait for error response
ch := make(chan error, 1)
go func() {
// send the first message
ch <- stream.Send(req.Body())
}()
var grr error
select {
case err := <-ch:
grr = err
case <-ctx.Done():
grr = errors.Timeout("go.micro.client", fmt.Sprintf("%v", ctx.Err()))
}
if grr != nil {
// set the error
stream.Lock()
stream.err = grr
stream.Unlock()
// close the stream
stream.Close()
return nil, grr
}
return stream, nil
}
func (r *rpcClient) Init(opts ...Option) error {
size := r.opts.PoolSize
ttl := r.opts.PoolTTL
tr := r.opts.Transport
for _, o := range opts {
o(&r.opts)
}
// 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()
// create new pool
r.pool = pool.NewPool(
pool.Size(r.opts.PoolSize),
pool.TTL(r.opts.PoolTTL),
pool.Transport(r.opts.Transport),
)
}
return nil
}
func (r *rpcClient) Options() Options {
return r.opts
}
func (r *rpcClient) Call(ctx context.Context, request Request, response interface{}, opts ...CallOption) error {
// make a copy of call opts
callOpts := r.opts.CallOptions
for _, opt := range opts {
opt(&callOpts)
}
// check if we already have a deadline
if d, ok := ctx.Deadline(); !ok {
// 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
// but we need to set the timeout we pass along
remaining := d.Sub(time.Now())
WithRequestTimeout(remaining)(&callOpts)
}
// should we noop right here?
select {
case <-ctx.Done():
return errors.Timeout("go.micro.client", fmt.Sprintf("%v", ctx.Err()))
default:
}
// make copy of call method
rcall := r.call
// wrap the call in reverse
for i := len(callOpts.CallWrappers); i > 0; i-- {
rcall = callOpts.CallWrappers[i-1](rcall)
}
// return errors.New("go.micro.client", "request timeout", 408)
call := func(i int) error {
// 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())
}
// only sleep if greater than 0
if t.Seconds() > 0 {
time.Sleep(t)
}
// use the router passed as a call option, or fallback to the rpc clients router
if callOpts.Router == nil {
callOpts.Router = r.opts.Router
}
// use the selector passed as a call option, or fallback to the rpc clients selector
if callOpts.Selector == nil {
callOpts.Selector = r.opts.Selector
}
// lookup the route to send the request via
route, err := LookupRoute(request, callOpts)
if err != nil {
return err
}
// pass a node to enable backwards comparability as changing the
// call func would be a breaking change.
// todo v3: change the call func to accept a route
node := &registry.Node{Address: route.Address, Metadata: route.Metadata}
// make the call
err = rcall(ctx, node, request, response, callOpts)
// record the result of the call to inform future routing decisions
r.opts.Selector.Record(*route, err)
return err
}
// get the retries
retries := callOpts.Retries
// disable retries when using a proxy
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++ {
go func(i int) {
ch <- call(i)
}(i)
select {
case <-ctx.Done():
return errors.Timeout("go.micro.client", fmt.Sprintf("call timeout: %v", ctx.Err()))
case err := <-ch:
// if the call succeeded lets bail early
if err == nil {
return nil
}
retry, rerr := callOpts.Retry(ctx, request, i, err)
if rerr != nil {
return rerr
}
if !retry {
return err
}
gerr = err
}
}
return gerr
}
func (r *rpcClient) Stream(ctx context.Context, request Request, opts ...CallOption) (Stream, error) {
// make a copy of call opts
callOpts := r.opts.CallOptions
for _, opt := range opts {
opt(&callOpts)
}
// should we noop right here?
select {
case <-ctx.Done():
return nil, errors.Timeout("go.micro.client", fmt.Sprintf("%v", ctx.Err()))
default:
}
call := func(i int) (Stream, error) {
// 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())
}
// only sleep if greater than 0
if t.Seconds() > 0 {
time.Sleep(t)
}
// use the router passed as a call option, or fallback to the rpc clients router
if callOpts.Router == nil {
callOpts.Router = r.opts.Router
}
// use the selector passed as a call option, or fallback to the rpc clients selector
if callOpts.Selector == nil {
callOpts.Selector = r.opts.Selector
}
// lookup the route to send the request via
route, err := LookupRoute(request, callOpts)
if err != nil {
return nil, err
}
// pass a node to enable backwards compatability as changing the
// call func would be a breaking change.
// todo v3: change the call func to accept a route
node := &registry.Node{Address: route.Address, Metadata: route.Metadata}
// perform the call
stream, err := r.stream(ctx, node, request, callOpts)
// record the result of the call to inform future routing decisions
r.opts.Selector.Record(*route, err)
return stream, err
}
type response struct {
stream Stream
err error
}
// get the retries
retries := callOpts.Retries
// disable retries when using a proxy
if _, _, ok := net.Proxy(request.Service(), callOpts.Address); ok {
retries = 0
}
ch := make(chan response, retries+1)
var grr error
for i := 0; i <= retries; i++ {
go func(i int) {
s, err := call(i)
ch <- response{s, err}
}(i)
select {
case <-ctx.Done():
return nil, errors.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 {
return rsp.stream, nil
}
retry, rerr := callOpts.Retry(ctx, request, i, rsp.err)
if rerr != nil {
return nil, rerr
}
if !retry {
return nil, rsp.err
}
grr = rsp.err
}
}
return nil, grr
}
func (r *rpcClient) Publish(ctx context.Context, msg Message, opts ...PublishOption) error {
options := PublishOptions{
Context: context.Background(),
}
for _, o := range opts {
o(&options)
}
md, ok := metadata.FromContext(ctx)
if !ok {
md = make(map[string]string)
}
id := uuid.New().String()
md["Content-Type"] = msg.ContentType()
md["Micro-Topic"] = msg.Topic()
md["Micro-Id"] = id
// set the topic
topic := msg.Topic()
// get the exchange
if len(options.Exchange) > 0 {
topic = options.Exchange
}
// encode message body
cf, err := r.newCodec(msg.ContentType())
if err != nil {
return errors.InternalServerError("go.micro.client", err.Error())
}
var body []byte
// passed in raw data
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{
Target: topic,
Type: codec.Event,
Header: map[string]string{
"Micro-Id": id,
"Micro-Topic": msg.Topic(),
},
}, msg.Payload()); err != nil {
return errors.InternalServerError("go.micro.client", err.Error())
}
// set the body
body = b.Bytes()
}
if !r.once.Load().(bool) {
if err = r.opts.Broker.Connect(); err != nil {
return errors.InternalServerError("go.micro.client", err.Error())
}
r.once.Store(true)
}
return r.opts.Broker.Publish(topic, &broker.Message{
Header: md,
Body: body,
}, broker.PublishContext(options.Context))
}
func (r *rpcClient) NewMessage(topic string, message interface{}, opts ...MessageOption) Message {
return newMessage(topic, message, r.opts.ContentType, opts...)
}
func (r *rpcClient) NewRequest(service, method string, request interface{}, reqOpts ...RequestOption) Request {
return newRequest(service, method, request, r.opts.ContentType, reqOpts...)
}
func (r *rpcClient) String() string {
return "mucp"
}

View File

@ -11,9 +11,9 @@ import (
import ( import (
context "context" context "context"
api "github.com/micro/go-micro/v2/api" api "github.com/micro/go-micro/v3/api"
client "github.com/micro/go-micro/v2/client" client "github.com/micro/go-micro/v3/client"
server "github.com/micro/go-micro/v2/server" server "github.com/micro/go-micro/v3/server"
) )
// Reference imports to suppress errors if they are not otherwise used. // Reference imports to suppress errors if they are not otherwise used.

65
client/test_request.go Normal file
View File

@ -0,0 +1,65 @@
package client
import (
"github.com/micro/go-micro/v3/codec"
)
type testRequest struct {
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 {
var opts RequestOptions
for _, o := range reqOpts {
o(&opts)
}
// set the content-type specified
if len(opts.ContentType) > 0 {
contentType = opts.ContentType
}
return &testRequest{
service: service,
method: endpoint,
endpoint: endpoint,
body: request,
contentType: contentType,
opts: opts,
}
}
func (r *testRequest) ContentType() string {
return r.contentType
}
func (r *testRequest) Service() string {
return r.service
}
func (r *testRequest) Method() string {
return r.method
}
func (r *testRequest) Endpoint() string {
return r.endpoint
}
func (r *testRequest) Body() interface{} {
return r.body
}
func (r *testRequest) Codec() codec.Writer {
return r.codec
}
func (r *testRequest) Stream() bool {
return r.opts.Stream
}

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