Initial QSfera import

This commit is contained in:
Курнат Андрей
2026-06-07 10:20:04 +03:00
commit 2315f25754
16485 changed files with 4826827 additions and 0 deletions
+29
View File
@@ -0,0 +1,29 @@
package server
import (
"context"
"sync"
)
type serverKey struct{}
type wgKey struct{}
func wait(ctx context.Context) *sync.WaitGroup {
if ctx == nil {
return nil
}
wg, ok := ctx.Value(wgKey{}).(*sync.WaitGroup)
if !ok {
return nil
}
return wg
}
func FromContext(ctx context.Context) (Server, bool) {
c, ok := ctx.Value(serverKey{}).(Server)
return c, ok
}
func NewContext(ctx context.Context, s Server) context.Context {
return context.WithValue(ctx, serverKey{}, s)
}
+131
View File
@@ -0,0 +1,131 @@
package server
import (
"fmt"
"reflect"
"strings"
"go-micro.dev/v4/registry"
)
func extractValue(v reflect.Type, d int) *registry.Value {
if d == 3 {
return nil
}
if v == nil {
return nil
}
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
arg := &registry.Value{
Name: v.Name(),
Type: v.Name(),
}
switch v.Kind() {
case reflect.Struct:
for i := 0; i < v.NumField(); i++ {
f := v.Field(i)
if f.PkgPath != "" {
continue
}
val := extractValue(f.Type, d+1)
if val == nil {
continue
}
// if we can find a json tag use it
if tags := f.Tag.Get("json"); len(tags) > 0 {
parts := strings.Split(tags, ",")
if parts[0] == "-" || parts[0] == "omitempty" {
continue
}
val.Name = parts[0]
}
// if there's no name default it
if len(val.Name) == 0 {
val.Name = v.Field(i).Name
}
// still no name then continue
if len(val.Name) == 0 {
continue
}
arg.Values = append(arg.Values, val)
}
case reflect.Slice:
p := v.Elem()
if p.Kind() == reflect.Ptr {
p = p.Elem()
}
arg.Type = "[]" + p.Name()
}
return arg
}
func extractEndpoint(method reflect.Method) *registry.Endpoint {
if method.PkgPath != "" {
return nil
}
var rspType, reqType reflect.Type
var stream bool
mt := method.Type
switch mt.NumIn() {
case 3:
reqType = mt.In(1)
rspType = mt.In(2)
case 4:
reqType = mt.In(2)
rspType = mt.In(3)
default:
return nil
}
// are we dealing with a stream?
switch rspType.Kind() {
case reflect.Func, reflect.Interface:
stream = true
}
request := extractValue(reqType, 0)
response := extractValue(rspType, 0)
ep := &registry.Endpoint{
Name: method.Name,
Request: request,
Response: response,
Metadata: make(map[string]string),
}
// set endpoint metadata for stream
if stream {
ep.Metadata = map[string]string{
"stream": fmt.Sprintf("%v", stream),
}
}
return ep
}
func extractSubValue(typ reflect.Type) *registry.Value {
var reqType reflect.Type
switch typ.NumIn() {
case 1:
reqType = typ.In(0)
case 2:
reqType = typ.In(1)
case 3:
reqType = typ.In(2)
default:
return nil
}
return extractValue(reqType, 0)
}
+80
View File
@@ -0,0 +1,80 @@
package server
import "context"
type HandlerOption func(*HandlerOptions)
type HandlerOptions struct {
Metadata map[string]map[string]string
Internal bool
}
type SubscriberOption func(*SubscriberOptions)
type SubscriberOptions struct {
Context context.Context
Queue string
// AutoAck defaults to true. When a handler returns
// with a nil error the message is acked.
AutoAck bool
Internal bool
}
// EndpointMetadata is a Handler option that allows metadata to be added to
// individual endpoints.
func EndpointMetadata(name string, md map[string]string) HandlerOption {
return func(o *HandlerOptions) {
o.Metadata[name] = md
}
}
// Internal Handler options specifies that a handler is not advertised
// to the discovery system. In the future this may also limit request
// to the internal network or authorized user.
func InternalHandler(b bool) HandlerOption {
return func(o *HandlerOptions) {
o.Internal = b
}
}
// Internal Subscriber options specifies that a subscriber is not advertised
// to the discovery system.
func InternalSubscriber(b bool) SubscriberOption {
return func(o *SubscriberOptions) {
o.Internal = b
}
}
func NewSubscriberOptions(opts ...SubscriberOption) SubscriberOptions {
opt := SubscriberOptions{
AutoAck: true,
Context: context.Background(),
}
for _, o := range opts {
o(&opt)
}
return opt
}
// DisableAutoAck will disable auto acking of messages
// after they have been handled.
func DisableAutoAck() SubscriberOption {
return func(o *SubscriberOptions) {
o.AutoAck = false
}
}
// Shared queue name distributed messages across subscribers.
func SubscriberQueue(n string) SubscriberOption {
return func(o *SubscriberOptions) {
o.Queue = n
}
}
// SubscriberContext set context options to allow broker SubscriberOption passed.
func SubscriberContext(ctx context.Context) SubscriberOption {
return func(o *SubscriberOptions) {
o.Context = ctx
}
}
+306
View File
@@ -0,0 +1,306 @@
package server
import (
"context"
"crypto/tls"
"sync"
"time"
"go-micro.dev/v4/broker"
"go-micro.dev/v4/codec"
"go-micro.dev/v4/debug/trace"
"go-micro.dev/v4/logger"
"go-micro.dev/v4/registry"
"go-micro.dev/v4/transport"
)
type RouterOptions struct {
Logger logger.Logger
}
type RouterOption func(o *RouterOptions)
func NewRouterOptions(opt ...RouterOption) RouterOptions {
opts := RouterOptions{
Logger: logger.DefaultLogger,
}
for _, o := range opt {
o(&opts)
}
return opts
}
// WithRouterLogger sets the underline router logger.
func WithRouterLogger(l logger.Logger) RouterOption {
return func(o *RouterOptions) {
o.Logger = l
}
}
type Options struct {
Logger logger.Logger
Broker broker.Broker
Registry registry.Registry
Tracer trace.Tracer
Transport transport.Transport
// Other options for implementations of the interface
// can be stored in a context
Context context.Context
// The router for requests
Router Router
// RegisterCheck runs a check function before registering the service
RegisterCheck func(context.Context) error
Metadata map[string]string
// TLSConfig specifies tls.Config for secure serving
TLSConfig *tls.Config
Codecs map[string]codec.NewCodec
Name string
Id string
Version string
Advertise string
Address string
HdlrWrappers []HandlerWrapper
ListenOptions []transport.ListenOption
SubWrappers []SubscriberWrapper
// The interval on which to register
RegisterInterval time.Duration
// The register expiry time
RegisterTTL time.Duration
}
// NewOptions creates new server options.
func NewOptions(opt ...Option) Options {
opts := Options{
Codecs: make(map[string]codec.NewCodec),
Metadata: map[string]string{},
RegisterInterval: DefaultRegisterInterval,
RegisterTTL: DefaultRegisterTTL,
Logger: logger.DefaultLogger,
}
for _, o := range opt {
o(&opts)
}
if opts.Broker == nil {
opts.Broker = broker.DefaultBroker
}
if opts.Registry == nil {
opts.Registry = registry.DefaultRegistry
}
if opts.Transport == nil {
opts.Transport = transport.DefaultTransport
}
if opts.RegisterCheck == nil {
opts.RegisterCheck = DefaultRegisterCheck
}
if len(opts.Address) == 0 {
opts.Address = DefaultAddress
}
if len(opts.Name) == 0 {
opts.Name = DefaultName
}
if len(opts.Id) == 0 {
opts.Id = DefaultId
}
if len(opts.Version) == 0 {
opts.Version = DefaultVersion
}
return opts
}
// Server name.
func Name(n string) Option {
return func(o *Options) {
o.Name = n
}
}
// Unique server id.
func Id(id string) Option {
return func(o *Options) {
o.Id = id
}
}
// Version of the service.
func Version(v string) Option {
return func(o *Options) {
o.Version = v
}
}
// Address to bind to - host:port.
func Address(a string) Option {
return func(o *Options) {
o.Address = a
}
}
// The address to advertise for discovery - host:port.
func Advertise(a string) Option {
return func(o *Options) {
o.Advertise = a
}
}
// Broker to use for pub/sub.
func Broker(b broker.Broker) Option {
return func(o *Options) {
o.Broker = b
}
}
// Codec to use to encode/decode requests for a given content type.
func Codec(contentType string, c codec.NewCodec) Option {
return func(o *Options) {
o.Codecs[contentType] = c
}
}
// Context specifies a context for the service.
// Can be used to signal shutdown of the service
// Can be used for extra option values.
func Context(ctx context.Context) Option {
return func(o *Options) {
o.Context = ctx
}
}
// Registry used for discovery.
func Registry(r registry.Registry) Option {
return func(o *Options) {
o.Registry = r
}
}
// Tracer mechanism for distributed tracking.
func Tracer(t trace.Tracer) Option {
return func(o *Options) {
o.Tracer = t
}
}
// Transport mechanism for communication e.g http, rabbitmq, etc.
func Transport(t transport.Transport) Option {
return func(o *Options) {
o.Transport = t
}
}
// Metadata associated with the server.
func Metadata(md map[string]string) Option {
return func(o *Options) {
o.Metadata = md
}
}
// RegisterCheck run func before registry service.
func RegisterCheck(fn func(context.Context) error) Option {
return func(o *Options) {
o.RegisterCheck = fn
}
}
// Register the service with a TTL.
func RegisterTTL(t time.Duration) Option {
return func(o *Options) {
o.RegisterTTL = t
}
}
// Register the service with at interval.
func RegisterInterval(t time.Duration) Option {
return func(o *Options) {
o.RegisterInterval = t
}
}
// TLSConfig specifies a *tls.Config.
func TLSConfig(t *tls.Config) Option {
return func(o *Options) {
// set the internal tls
o.TLSConfig = t
// set the default transport if one is not
// already set. Required for Init call below.
if o.Transport == nil {
o.Transport = transport.DefaultTransport
}
// set the transport tls
o.Transport.Init(
transport.Secure(true),
transport.TLSConfig(t),
)
}
}
// WithRouter sets the request router.
func WithRouter(r Router) Option {
return func(o *Options) {
o.Router = r
}
}
// WithLogger sets the underline logger.
func WithLogger(l logger.Logger) Option {
return func(o *Options) {
o.Logger = l
}
}
// Wait tells the server to wait for requests to finish before exiting
// If `wg` is nil, server only wait for completion of rpc handler.
// For user need finer grained control, pass a concrete `wg` here, server will
// wait against it on stop.
func Wait(wg *sync.WaitGroup) Option {
return func(o *Options) {
if o.Context == nil {
o.Context = context.Background()
}
if wg == nil {
wg = new(sync.WaitGroup)
}
o.Context = context.WithValue(o.Context, wgKey{}, wg)
}
}
// Adds a handler Wrapper to a list of options passed into the server.
func WrapHandler(w HandlerWrapper) Option {
return func(o *Options) {
o.HdlrWrappers = append(o.HdlrWrappers, w)
}
}
// Adds a subscriber Wrapper to a list of options passed into the server.
func WrapSubscriber(w SubscriberWrapper) Option {
return func(o *Options) {
o.SubWrappers = append(o.SubWrappers, w)
}
}
// Add transport.ListenOption to the ListenOptions list, when using it, it will be passed to the
// httpTransport.Listen() method.
func ListenOption(option transport.ListenOption) Option {
return func(o *Options) {
o.ListenOptions = append(o.ListenOptions, option)
}
}
+362
View File
@@ -0,0 +1,362 @@
package server
import (
"bytes"
"sync"
"github.com/oxtoacart/bpool"
"github.com/pkg/errors"
"go-micro.dev/v4/codec"
raw "go-micro.dev/v4/codec/bytes"
"go-micro.dev/v4/codec/grpc"
"go-micro.dev/v4/codec/json"
"go-micro.dev/v4/codec/jsonrpc"
"go-micro.dev/v4/codec/proto"
"go-micro.dev/v4/codec/protorpc"
"go-micro.dev/v4/transport"
"go-micro.dev/v4/transport/headers"
)
type rpcCodec struct {
socket transport.Socket
codec codec.Codec
req *transport.Message
buf *readWriteCloser
first chan bool
protocol string
// check if we're the first
sync.RWMutex
}
type readWriteCloser struct {
wbuf *bytes.Buffer
rbuf *bytes.Buffer
sync.RWMutex
}
var (
// DefaultContentType is the default codec content type.
DefaultContentType = "application/protobuf"
DefaultCodecs = map[string]codec.NewCodec{
"application/grpc": grpc.NewCodec,
"application/grpc+json": grpc.NewCodec,
"application/grpc+proto": grpc.NewCodec,
"application/json": json.NewCodec,
"application/json-rpc": jsonrpc.NewCodec,
"application/protobuf": proto.NewCodec,
"application/proto-rpc": protorpc.NewCodec,
"application/octet-stream": raw.NewCodec,
}
// TODO: remove legacy codec list.
defaultCodecs = map[string]codec.NewCodec{
"application/json": jsonrpc.NewCodec,
"application/json-rpc": jsonrpc.NewCodec,
"application/protobuf": protorpc.NewCodec,
"application/proto-rpc": protorpc.NewCodec,
"application/octet-stream": protorpc.NewCodec,
}
// the local buffer pool.
bufferPool = bpool.NewSizedBufferPool(32, 1)
)
func (rwc *readWriteCloser) Read(p []byte) (n int, err error) {
rwc.RLock()
defer rwc.RUnlock()
return rwc.rbuf.Read(p)
}
func (rwc *readWriteCloser) Write(p []byte) (n int, err error) {
rwc.Lock()
defer rwc.Unlock()
return rwc.wbuf.Write(p)
}
func (rwc *readWriteCloser) Close() error {
return nil
}
func getHeader(hdr string, md map[string]string) string {
if hd := md[hdr]; len(hd) > 0 {
return hd
}
return md["X-"+hdr]
}
func getHeaders(m *codec.Message) {
set := func(v, hdr string) string {
if len(v) > 0 {
return v
}
return m.Header[hdr]
}
m.Id = set(m.Id, headers.ID)
m.Error = set(m.Error, headers.Error)
m.Endpoint = set(m.Endpoint, headers.Endpoint)
m.Method = set(m.Method, headers.Method)
m.Target = set(m.Target, headers.Request)
// TODO: remove this cruft
if len(m.Endpoint) == 0 {
m.Endpoint = m.Method
}
}
func setHeaders(m, r *codec.Message) {
set := func(hdr, v string) {
if len(v) == 0 {
return
}
m.Header[hdr] = v
m.Header["X-"+hdr] = v
}
// set headers
set(headers.ID, r.Id)
set(headers.Request, r.Target)
set(headers.Method, r.Method)
set(headers.Endpoint, r.Endpoint)
set(headers.Error, r.Error)
}
// setupProtocol sets up the old protocol.
func setupProtocol(msg *transport.Message) codec.NewCodec {
service := getHeader(headers.Request, msg.Header)
method := getHeader(headers.Method, msg.Header)
endpoint := getHeader(headers.Endpoint, msg.Header)
protocol := getHeader(headers.Protocol, msg.Header)
target := getHeader(headers.Target, msg.Header)
topic := getHeader(headers.Message, msg.Header)
// if the protocol exists (mucp) do nothing
if len(protocol) > 0 {
return nil
}
// newer method of processing messages over transport
if len(topic) > 0 {
return nil
}
// if no service/method/endpoint then it's the old protocol
if len(service) == 0 && len(method) == 0 && len(endpoint) == 0 {
return defaultCodecs[msg.Header["Content-Type"]]
}
// old target method specified
if len(target) > 0 {
return defaultCodecs[msg.Header["Content-Type"]]
}
// no method then set to endpoint
if len(method) == 0 {
msg.Header[headers.Method] = endpoint
}
// no endpoint then set to method
if len(endpoint) == 0 {
msg.Header[headers.Endpoint] = method
}
return nil
}
func newRPCCodec(req *transport.Message, socket transport.Socket, c codec.NewCodec) codec.Codec {
rwc := &readWriteCloser{
rbuf: bufferPool.Get(),
wbuf: bufferPool.Get(),
}
r := &rpcCodec{
buf: rwc,
codec: c(rwc),
req: req,
socket: socket,
protocol: "mucp",
first: make(chan bool),
}
// if grpc pre-load the buffer
// TODO: remove this terrible hack
switch r.codec.String() {
case "grpc":
// write the body
rwc.rbuf.Write(req.Body)
r.protocol = "grpc"
default:
// first is not preloaded
close(r.first)
}
return r
}
func (c *rpcCodec) ReadHeader(r *codec.Message, t codec.MessageType) error {
// the initial message
mmsg := codec.Message{
Header: c.req.Header,
Body: c.req.Body,
}
// first message could be pre-loaded
select {
case <-c.first:
// not the first
var tm transport.Message
// read off the socket
if err := c.socket.Recv(&tm); err != nil {
return err
}
// reset the read buffer
c.buf.rbuf.Reset()
// write the body to the buffer
if _, err := c.buf.rbuf.Write(tm.Body); err != nil {
return err
}
// set the message header
mmsg.Header = tm.Header
// set the message body
mmsg.Body = tm.Body
// set req
c.req = &tm
default:
// we need to lock here to prevent race conditions
// and we make use of a channel otherwise because
// this does not result in a context switch
// locking to check c.first on every call to ReadHeader
// would otherwise drastically slow the code execution
c.Lock()
// recheck before closing because the select statement
// above is not thread safe, so thread safety here is
// mandatory
select {
case <-c.first:
default:
// disable first
close(c.first)
}
// now unlock and we never need this again
c.Unlock()
}
// set some internal things
getHeaders(&mmsg)
// read header via codec
if err := c.codec.ReadHeader(&mmsg, codec.Request); err != nil {
return err
}
// fallback for 0.14 and older
if len(mmsg.Endpoint) == 0 {
mmsg.Endpoint = mmsg.Method
}
// set message
*r = mmsg
return nil
}
func (c *rpcCodec) ReadBody(b interface{}) error {
// don't read empty body
if len(c.req.Body) == 0 {
return nil
}
// read raw data
if v, ok := b.(*raw.Frame); ok {
v.Data = c.req.Body
return nil
}
// decode the usual way
return c.codec.ReadBody(b)
}
func (c *rpcCodec) Write(r *codec.Message, b interface{}) error {
c.buf.wbuf.Reset()
// create a new message
m := &codec.Message{
Target: r.Target,
Method: r.Method,
Endpoint: r.Endpoint,
Id: r.Id,
Error: r.Error,
Type: r.Type,
Header: r.Header,
}
if m.Header == nil {
m.Header = map[string]string{}
}
setHeaders(m, r)
// the body being sent
var body []byte
// is it a raw frame?
if v, ok := b.(*raw.Frame); ok {
body = v.Data
// if we have encoded data just send it
} else if len(r.Body) > 0 {
body = r.Body
// write the body to codec
} else if err := c.codec.Write(m, b); err != nil {
c.buf.wbuf.Reset()
// write an error if it failed
m.Error = errors.Wrapf(err, "Unable to encode body").Error()
m.Header[headers.Error] = m.Error
// no body to write
if err := c.codec.Write(m, nil); err != nil {
return err
}
} else {
// set the body
body = c.buf.wbuf.Bytes()
}
// Set content type if theres content
if len(body) > 0 {
m.Header["Content-Type"] = c.req.Header["Content-Type"]
}
// send on the socket
return c.socket.Send(&transport.Message{
Header: m.Header,
Body: body,
})
}
func (c *rpcCodec) Close() error {
// close the codec
c.codec.Close()
// close the socket
err := c.socket.Close()
// put back the buffers
bufferPool.Put(c.buf.rbuf)
bufferPool.Put(c.buf.wbuf)
// return the error
return err
}
func (c *rpcCodec) String() string {
return c.protocol
}
+39
View File
@@ -0,0 +1,39 @@
package server
import (
"go-micro.dev/v4/broker"
"go-micro.dev/v4/transport"
"go-micro.dev/v4/transport/headers"
)
// event is a broker event we handle on the server transport.
type event struct {
err error
message *broker.Message
}
func (e *event) Ack() error {
// there is no ack support
return nil
}
func (e *event) Message() *broker.Message {
return e.message
}
func (e *event) Error() error {
return e.err
}
func (e *event) Topic() string {
return e.message.Header[headers.Message]
}
func newEvent(msg transport.Message) *event {
return &event{
message: &broker.Message{
Header: msg.Header,
Body: msg.Body,
},
}
}
+144
View File
@@ -0,0 +1,144 @@
package server
import (
"context"
"fmt"
"go-micro.dev/v4/broker"
raw "go-micro.dev/v4/codec/bytes"
log "go-micro.dev/v4/logger"
"go-micro.dev/v4/metadata"
"go-micro.dev/v4/transport/headers"
)
// HandleEvent handles inbound messages to the service directly.
// These events are a result of registering to the topic with the service name.
// TODO: handle requests from an event. We won't send a response.
func (s *rpcServer) HandleEvent(e broker.Event) error {
// formatting horrible cruft
msg := e.Message()
if msg.Header == nil {
msg.Header = make(map[string]string)
}
contentType, ok := msg.Header["Content-Type"]
if !ok || len(contentType) == 0 {
msg.Header["Content-Type"] = DefaultContentType
contentType = DefaultContentType
}
cf, err := s.newCodec(contentType)
if err != nil {
return err
}
header := make(map[string]string, len(msg.Header))
for k, v := range msg.Header {
header[k] = v
}
// create context
ctx := metadata.NewContext(context.Background(), header)
// TODO: inspect message header for Micro-Service & Micro-Topic
rpcMsg := &rpcMessage{
topic: msg.Header[headers.Message],
contentType: contentType,
payload: &raw.Frame{Data: msg.Body},
codec: cf,
header: msg.Header,
body: msg.Body,
}
// if the router is present then execute it
r := Router(s.router)
if s.opts.Router != nil {
// create a wrapped function
handler := s.opts.Router.ProcessMessage
// execute the wrapper for it
for i := len(s.opts.SubWrappers); i > 0; i-- {
handler = s.opts.SubWrappers[i-1](handler)
}
// set the router
r = rpcRouter{m: handler}
}
return r.ProcessMessage(ctx, rpcMsg)
}
func (s *rpcServer) NewSubscriber(topic string, sb interface{}, opts ...SubscriberOption) Subscriber {
return s.router.NewSubscriber(topic, sb, opts...)
}
func (s *rpcServer) Subscribe(sb Subscriber) error {
s.Lock()
defer s.Unlock()
sub, ok := sb.(*subscriber)
if !ok {
return fmt.Errorf("invalid subscriber: expected *subscriber")
}
if len(sub.handlers) == 0 {
return fmt.Errorf("invalid subscriber: no handler functions")
}
if err := validateSubscriber(sub); err != nil {
return err
}
// append to subscribers
// subs := s.subscribers[sub.Topic()]
// subs = append(subs, sub)
// router.subscribers[sub.Topic()] = subs
s.subscribers[sb] = nil
return nil
}
// subscribeServer will subscribe the server to the topic with its own name.
func (s *rpcServer) subscribeServer(config Options) error {
if s.opts.Router != nil {
sub, err := s.opts.Broker.Subscribe(config.Name, s.HandleEvent)
if err != nil {
return err
}
// Save the subscriber
s.subscriber = sub
}
return nil
}
// reSubscribe itterates over subscribers and re-subscribes then.
func (s *rpcServer) reSubscribe(config Options) error {
for sb := range s.subscribers {
var opts []broker.SubscribeOption
if queue := sb.Options().Queue; len(queue) > 0 {
opts = append(opts, broker.Queue(queue))
}
if ctx := sb.Options().Context; ctx != nil {
opts = append(opts, broker.SubscribeContext(ctx))
}
if !sb.Options().AutoAck {
opts = append(opts, broker.DisableAutoAck())
}
config.Logger.Logf(log.InfoLevel, "Subscribing to topic: %s", sb.Topic())
sub, err := config.Broker.Subscribe(sb.Topic(), s.HandleEvent, opts...)
if err != nil {
return err
}
s.subscribers[sb] = []broker.Subscriber{sub}
s.router.Subscribe(sb)
}
return nil
}
+65
View File
@@ -0,0 +1,65 @@
package server
import (
"reflect"
"go-micro.dev/v4/registry"
)
type RpcHandler struct {
handler interface{}
opts HandlerOptions
name string
endpoints []*registry.Endpoint
}
func NewRpcHandler(handler interface{}, opts ...HandlerOption) Handler {
options := HandlerOptions{
Metadata: make(map[string]map[string]string),
}
for _, o := range opts {
o(&options)
}
typ := reflect.TypeOf(handler)
hdlr := reflect.ValueOf(handler)
name := reflect.Indirect(hdlr).Type().Name()
var endpoints []*registry.Endpoint
for m := 0; m < typ.NumMethod(); m++ {
if e := extractEndpoint(typ.Method(m)); e != nil {
e.Name = name + "." + e.Name
for k, v := range options.Metadata[e.Name] {
e.Metadata[k] = v
}
endpoints = append(endpoints, e)
}
}
return &RpcHandler{
name: name,
handler: handler,
endpoints: endpoints,
opts: options,
}
}
func (r *RpcHandler) Name() string {
return r.name
}
func (r *RpcHandler) Handler() interface{} {
return r.handler
}
func (r *RpcHandler) Endpoints() []*registry.Endpoint {
return r.endpoints
}
func (r *RpcHandler) Options() HandlerOptions {
return r.opts
}
+101
View File
@@ -0,0 +1,101 @@
package server
import (
"fmt"
"sync"
"go-micro.dev/v4/codec"
"go-micro.dev/v4/registry"
)
// setRegistered will set the service as registered safely.
func (s *rpcServer) setRegistered(b bool) {
s.Lock()
defer s.Unlock()
s.registered = b
}
// isRegistered will check if the service has already been registered.
func (s *rpcServer) isRegistered() bool {
s.RLock()
defer s.RUnlock()
return s.registered
}
// setStarted will set started state safely.
func (s *rpcServer) setStarted(b bool) {
s.Lock()
defer s.Unlock()
s.started = b
}
// isStarted will check if the service has already been started.
func (s *rpcServer) isStarted() bool {
s.RLock()
defer s.RUnlock()
return s.started
}
// setWg will set the waitgroup safely.
func (s *rpcServer) setWg(wg *sync.WaitGroup) {
s.Lock()
defer s.Unlock()
s.wg = wg
}
// getWaitgroup returns the global waitgroup safely.
func (s *rpcServer) getWg() *sync.WaitGroup {
s.RLock()
defer s.RUnlock()
return s.wg
}
// setOptsAddr will set the address in the service options safely.
func (s *rpcServer) setOptsAddr(addr string) {
s.Lock()
defer s.Unlock()
s.opts.Address = addr
}
func (s *rpcServer) getCachedService() *registry.Service {
s.RLock()
defer s.RUnlock()
return s.rsvc
}
func (s *rpcServer) Options() Options {
s.RLock()
defer s.RUnlock()
return s.opts
}
// swapAddr swaps the address found in the config and the transport address.
func (s *rpcServer) swapAddr(config Options, addr string) string {
s.Lock()
defer s.Unlock()
a := config.Address
s.opts.Address = addr
return a
}
func (s *rpcServer) newCodec(contentType string) (codec.NewCodec, error) {
if cf, ok := s.opts.Codecs[contentType]; ok {
return cf, nil
}
if cf, ok := DefaultCodecs[contentType]; ok {
return cf, nil
}
return nil, fmt.Errorf("unsupported Content-Type: %s", contentType)
}
+107
View File
@@ -0,0 +1,107 @@
package server
import (
"bytes"
"go-micro.dev/v4/codec"
"go-micro.dev/v4/transport"
"go-micro.dev/v4/util/buf"
)
type rpcRequest struct {
socket transport.Socket
codec codec.Codec
rawBody interface{}
header map[string]string
service string
method string
endpoint string
contentType string
body []byte
stream bool
first bool
}
type rpcMessage struct {
payload interface{}
header map[string]string
codec codec.NewCodec
topic string
contentType string
body []byte
}
func (r *rpcRequest) Codec() codec.Reader {
return r.codec
}
func (r *rpcRequest) ContentType() string {
return r.contentType
}
func (r *rpcRequest) Service() string {
return r.service
}
func (r *rpcRequest) Method() string {
return r.method
}
func (r *rpcRequest) Endpoint() string {
return r.endpoint
}
func (r *rpcRequest) Header() map[string]string {
return r.header
}
func (r *rpcRequest) Body() interface{} {
return r.rawBody
}
func (r *rpcRequest) Read() ([]byte, error) {
// got a body
if r.first {
b := r.body
r.first = false
return b, nil
}
var msg transport.Message
err := r.socket.Recv(&msg)
if err != nil {
return nil, err
}
r.header = msg.Header
return msg.Body, nil
}
func (r *rpcRequest) Stream() bool {
return r.stream
}
func (r *rpcMessage) ContentType() string {
return r.contentType
}
func (r *rpcMessage) Topic() string {
return r.topic
}
func (r *rpcMessage) Payload() interface{} {
return r.payload
}
func (r *rpcMessage) Header() map[string]string {
return r.header
}
func (r *rpcMessage) Body() []byte {
return r.body
}
func (r *rpcMessage) Codec() codec.Reader {
b := buf.New(bytes.NewBuffer(r.body))
return r.codec(b)
}
+35
View File
@@ -0,0 +1,35 @@
package server
import (
"net/http"
"go-micro.dev/v4/codec"
"go-micro.dev/v4/transport"
)
type rpcResponse struct {
header map[string]string
socket transport.Socket
codec codec.Codec
}
func (r *rpcResponse) Codec() codec.Writer {
return r.codec
}
func (r *rpcResponse) WriteHeader(hdr map[string]string) {
for k, v := range hdr {
r.header[k] = v
}
}
func (r *rpcResponse) Write(b []byte) error {
if _, ok := r.header["Content-Type"]; !ok {
r.header["Content-Type"] = http.DetectContentType(b)
}
return r.socket.Send(&transport.Message{
Header: r.header,
Body: b,
})
}
+648
View File
@@ -0,0 +1,648 @@
package server
import (
"context"
"errors"
"fmt"
"io"
"reflect"
"runtime/debug"
"strings"
"sync"
"unicode"
"unicode/utf8"
"go-micro.dev/v4/codec"
merrors "go-micro.dev/v4/errors"
log "go-micro.dev/v4/logger"
)
var (
errLastStreamResponse = errors.New("EOS")
// Precompute the reflect type for error. Can't use error directly
// because Typeof takes an empty interface value. This is annoying.
typeOfError = reflect.TypeOf((*error)(nil)).Elem()
)
type methodType struct {
ArgType reflect.Type
ReplyType reflect.Type
ContextType reflect.Type
method reflect.Method
sync.Mutex // protects counters
stream bool
}
type service struct {
typ reflect.Type // type of the receiver
method map[string]*methodType // registered methods
rcvr reflect.Value // receiver of methods for the service
name string // name of service
}
type request struct {
msg *codec.Message
next *request // for free list in Server
}
type response struct {
msg *codec.Message
next *response // for free list in Server
}
// router represents an RPC router.
type router struct {
ops RouterOptions
serviceMap map[string]*service
freeReq *request
freeResp *response
subscribers map[string][]*subscriber
name string
// handler wrappers
hdlrWrappers []HandlerWrapper
// subscriber wrappers
subWrappers []SubscriberWrapper
su sync.RWMutex
mu sync.Mutex // protects the serviceMap
reqLock sync.Mutex // protects freeReq
respLock sync.Mutex // protects freeResp
}
// rpcRouter encapsulates functions that become a Router.
type rpcRouter struct {
h func(context.Context, Request, interface{}) error
m func(context.Context, Message) error
}
func (r rpcRouter) ProcessMessage(ctx context.Context, msg Message) error {
return r.m(ctx, msg)
}
func (r rpcRouter) ServeRequest(ctx context.Context, req Request, rsp Response) error {
return r.h(ctx, req, rsp)
}
func newRpcRouter(opts ...RouterOption) *router {
return &router{
ops: NewRouterOptions(opts...),
serviceMap: make(map[string]*service),
subscribers: make(map[string][]*subscriber),
}
}
// Is this an exported - upper case - name?
func isExported(name string) bool {
rune, _ := utf8.DecodeRuneInString(name)
return unicode.IsUpper(rune)
}
// Is this type exported or a builtin?
func isExportedOrBuiltinType(t reflect.Type) bool {
for t.Kind() == reflect.Ptr {
t = t.Elem()
}
// PkgPath will be non-empty even for an exported type,
// so we need to check the type name as well.
return isExported(t.Name()) || t.PkgPath() == ""
}
// prepareMethod returns a methodType for the provided method or nil
// in case if the method was unsuitable.
func prepareMethod(method reflect.Method, logger log.Logger) *methodType {
mtype := method.Type
mname := method.Name
var replyType, argType, contextType reflect.Type
var stream bool
// Method must be exported.
if method.PkgPath != "" {
return nil
}
switch mtype.NumIn() {
case 3:
// assuming streaming
argType = mtype.In(2)
contextType = mtype.In(1)
stream = true
case 4:
// method that takes a context
argType = mtype.In(2)
replyType = mtype.In(3)
contextType = mtype.In(1)
default:
logger.Logf(log.ErrorLevel, "method %v of %v has wrong number of ins: %v", mname, mtype, mtype.NumIn())
return nil
}
if stream {
// check stream type
streamType := reflect.TypeOf((*Stream)(nil)).Elem()
if !argType.Implements(streamType) {
logger.Logf(log.ErrorLevel, "%v argument does not implement Stream interface: %v", mname, argType)
return nil
}
} else {
// if not stream check the replyType
// First arg need not be a pointer.
if !isExportedOrBuiltinType(argType) {
logger.Logf(log.ErrorLevel, "%v argument type not exported: %v", mname, argType)
return nil
}
if replyType.Kind() != reflect.Ptr {
logger.Logf(log.ErrorLevel, "method %v reply type not a pointer: %v", mname, replyType)
return nil
}
// Reply type must be exported.
if !isExportedOrBuiltinType(replyType) {
logger.Logf(log.ErrorLevel, "method %v reply type not exported: %v", mname, replyType)
return nil
}
}
// Method needs one out.
if mtype.NumOut() != 1 {
logger.Logf(log.ErrorLevel, "method %v has wrong number of outs: %v", mname, mtype.NumOut())
return nil
}
// The return type of the method must be error.
if returnType := mtype.Out(0); returnType != typeOfError {
logger.Logf(log.ErrorLevel, "method %v returns %v not error", mname, returnType.String())
return nil
}
return &methodType{method: method, ArgType: argType, ReplyType: replyType, ContextType: contextType, stream: stream}
}
func (router *router) sendResponse(sending sync.Locker, req *request, reply interface{}, cc codec.Writer, last bool) error {
msg := new(codec.Message)
msg.Type = codec.Response
resp := router.getResponse()
resp.msg = msg
resp.msg.Id = req.msg.Id
sending.Lock()
err := cc.Write(resp.msg, reply)
sending.Unlock()
router.freeResponse(resp)
return err
}
func (s *service) call(ctx context.Context, router *router, sending *sync.Mutex, mtype *methodType, req *request, argv, replyv reflect.Value, cc codec.Writer) error {
defer router.freeRequest(req)
function := mtype.method.Func
var returnValues []reflect.Value
r := &rpcRequest{
service: req.msg.Target,
contentType: req.msg.Header["Content-Type"],
method: req.msg.Method,
endpoint: req.msg.Endpoint,
body: req.msg.Body,
header: req.msg.Header,
}
// only set if not nil
if argv.IsValid() {
r.rawBody = argv.Interface()
}
if !mtype.stream {
fn := func(ctx context.Context, req Request, rsp interface{}) error {
returnValues = function.Call([]reflect.Value{s.rcvr, mtype.prepareContext(ctx), reflect.ValueOf(argv.Interface()), reflect.ValueOf(rsp)})
// The return value for the method is an error.
if err := returnValues[0].Interface(); err != nil {
return err.(error)
}
return nil
}
// wrap the handler
for i := len(router.hdlrWrappers); i > 0; i-- {
fn = router.hdlrWrappers[i-1](fn)
}
// execute handler
if err := fn(ctx, r, replyv.Interface()); err != nil {
return err
}
// send response
return router.sendResponse(sending, req, replyv.Interface(), cc, true)
}
// declare a local error to see if we errored out already
// keep track of the type, to make sure we return
// the same one consistently
rawStream := &rpcStream{
context: ctx,
codec: cc.(codec.Codec),
request: r,
id: req.msg.Id,
}
// Invoke the method, providing a new value for the reply.
fn := func(ctx context.Context, req Request, stream interface{}) error {
returnValues = function.Call([]reflect.Value{s.rcvr, mtype.prepareContext(ctx), reflect.ValueOf(stream)})
if err := returnValues[0].Interface(); err != nil {
// the function returned an error, we use that
return err.(error)
} else if serr := rawStream.Error(); serr == io.EOF || serr == io.ErrUnexpectedEOF {
return nil
} else {
// no error, we send the special EOS error
return errLastStreamResponse
}
}
// wrap the handler
for i := len(router.hdlrWrappers); i > 0; i-- {
fn = router.hdlrWrappers[i-1](fn)
}
// client.Stream request
r.stream = true
// execute handler
return fn(ctx, r, rawStream)
}
func (m *methodType) prepareContext(ctx context.Context) reflect.Value {
if contextv := reflect.ValueOf(ctx); contextv.IsValid() {
return contextv
}
return reflect.Zero(m.ContextType)
}
func (router *router) getRequest() *request {
router.reqLock.Lock()
defer router.reqLock.Unlock()
req := router.freeReq
if req == nil {
req = new(request)
} else {
router.freeReq = req.next
*req = request{}
}
return req
}
func (router *router) freeRequest(req *request) {
router.reqLock.Lock()
defer router.reqLock.Unlock()
req.next = router.freeReq
router.freeReq = req
}
func (router *router) getResponse() *response {
router.respLock.Lock()
defer router.respLock.Unlock()
resp := router.freeResp
if resp == nil {
resp = new(response)
} else {
router.freeResp = resp.next
*resp = response{}
}
return resp
}
func (router *router) freeResponse(resp *response) {
router.respLock.Lock()
defer router.respLock.Unlock()
resp.next = router.freeResp
router.freeResp = resp
}
func (router *router) readRequest(r Request) (service *service, mtype *methodType, req *request, argv, replyv reflect.Value, keepReading bool, err error) {
cc := r.Codec()
service, mtype, req, keepReading, err = router.readHeader(cc)
if err != nil {
if !keepReading {
return
}
// discard body
cc.ReadBody(nil)
return
}
// is it a streaming request? then we don't read the body
if mtype.stream {
if cc.(codec.Codec).String() != "grpc" {
cc.ReadBody(nil)
}
return
}
// Decode the argument value.
argIsValue := false // if true, need to indirect before calling.
if mtype.ArgType.Kind() == reflect.Ptr {
argv = reflect.New(mtype.ArgType.Elem())
} else {
argv = reflect.New(mtype.ArgType)
argIsValue = true
}
// argv guaranteed to be a pointer now.
if err = cc.ReadBody(argv.Interface()); err != nil {
return
}
if argIsValue {
argv = argv.Elem()
}
if !mtype.stream {
replyv = reflect.New(mtype.ReplyType.Elem())
}
return
}
func (router *router) readHeader(cc codec.Reader) (service *service, mtype *methodType, req *request, keepReading bool, err error) {
// Grab the request header.
msg := new(codec.Message)
msg.Type = codec.Request
req = router.getRequest()
req.msg = msg
err = cc.ReadHeader(msg, msg.Type)
if err != nil {
req = nil
if err == io.EOF || err == io.ErrUnexpectedEOF {
return
}
err = errors.New("rpc: router cannot decode request: " + err.Error())
return
}
// We read the header successfully. If we see an error now,
// we can still recover and move on to the next request.
keepReading = true
serviceMethod := strings.Split(req.msg.Endpoint, ".")
if len(serviceMethod) != 2 {
err = errors.New("rpc: service/endpoint request ill-formed: " + req.msg.Endpoint)
return
}
// Look up the request.
router.mu.Lock()
service = router.serviceMap[serviceMethod[0]]
router.mu.Unlock()
if service == nil {
err = errors.New("rpc: can't find service " + serviceMethod[0])
return
}
mtype = service.method[serviceMethod[1]]
if mtype == nil {
err = errors.New("rpc: can't find method " + serviceMethod[1])
}
return
}
func (router *router) NewHandler(h interface{}, opts ...HandlerOption) Handler {
return NewRpcHandler(h, opts...)
}
func (router *router) Handle(h Handler) error {
router.mu.Lock()
defer router.mu.Unlock()
if router.serviceMap == nil {
router.serviceMap = make(map[string]*service)
}
if len(h.Name()) == 0 {
return errors.New("rpc.Handle: handler has no name")
}
if !isExported(h.Name()) {
return errors.New("rpc.Handle: type " + h.Name() + " is not exported")
}
rcvr := h.Handler()
s := new(service)
s.typ = reflect.TypeOf(rcvr)
s.rcvr = reflect.ValueOf(rcvr)
// check name
if _, present := router.serviceMap[h.Name()]; present {
return errors.New("rpc.Handle: service already defined: " + h.Name())
}
s.name = h.Name()
s.method = make(map[string]*methodType)
// Install the methods
for m := 0; m < s.typ.NumMethod(); m++ {
method := s.typ.Method(m)
if mt := prepareMethod(method, router.ops.Logger); mt != nil {
s.method[method.Name] = mt
}
}
// Check there are methods
if len(s.method) == 0 {
return errors.New("rpc Register: type " + s.name + " has no exported methods of suitable type")
}
// save handler
router.serviceMap[s.name] = s
return nil
}
func (router *router) ServeRequest(ctx context.Context, r Request, rsp Response) error {
sending := new(sync.Mutex)
service, mtype, req, argv, replyv, keepReading, err := router.readRequest(r)
if err != nil {
if !keepReading {
return err
}
// send a response if we actually managed to read a header.
if req != nil {
router.freeRequest(req)
}
return err
}
return service.call(ctx, router, sending, mtype, req, argv, replyv, rsp.Codec())
}
func (router *router) NewSubscriber(topic string, handler interface{}, opts ...SubscriberOption) Subscriber {
return newSubscriber(topic, handler, opts...)
}
func (router *router) Subscribe(s Subscriber) error {
sub, ok := s.(*subscriber)
if !ok {
return fmt.Errorf("invalid subscriber: expected *subscriber")
}
if len(sub.handlers) == 0 {
return fmt.Errorf("invalid subscriber: no handler functions")
}
if err := validateSubscriber(sub); err != nil {
return err
}
router.su.Lock()
defer router.su.Unlock()
// append to subscribers
subs := router.subscribers[sub.Topic()]
subs = append(subs, sub)
router.subscribers[sub.Topic()] = subs
return nil
}
func (router *router) ProcessMessage(ctx context.Context, msg Message) (err error) {
defer func() {
// recover any panics
if r := recover(); r != nil {
router.ops.Logger.Logf(log.ErrorLevel, "panic recovered: %v", r)
router.ops.Logger.Log(log.ErrorLevel, string(debug.Stack()))
err = merrors.InternalServerError("go.micro.server", "panic recovered: %v", r)
}
}()
// get the subscribers by topic
router.su.RLock()
subs, ok := router.subscribers[msg.Topic()]
router.su.RUnlock()
if !ok {
log.Warnf("Subscriber not found for topic %s", msg.Topic())
return nil
}
var errResults []string
// we may have multiple subscribers for the topic
for _, sub := range subs {
// we may have multiple handlers per subscriber
for i := 0; i < len(sub.handlers); i++ {
// get the handler
handler := sub.handlers[i]
var isVal bool
var req reflect.Value
// check whether the handler is a pointer
if handler.reqType.Kind() == reflect.Ptr {
req = reflect.New(handler.reqType.Elem())
} else {
req = reflect.New(handler.reqType)
isVal = true
}
// if its a value get the element
if isVal {
req = req.Elem()
}
cc := msg.Codec()
// read the header. mostly a noop
if err = cc.ReadHeader(&codec.Message{}, codec.Event); err != nil {
return err
}
// make request value a pointer, if it's not already
reqVal := req.Interface()
if req.CanAddr() {
reqVal = req.Addr().Interface()
}
// read the body into the handler request value
if err = cc.ReadBody(reqVal); err != nil {
return err
}
// create the handler which will honor the SubscriberFunc type
fn := func(ctx context.Context, msg Message) error {
var vals []reflect.Value
if sub.typ.Kind() != reflect.Func {
vals = append(vals, sub.rcvr)
}
if handler.ctxType != nil {
vals = append(vals, reflect.ValueOf(ctx))
}
// values to pass the handler
vals = append(vals, reflect.ValueOf(msg.Payload()))
// execute the actuall call of the handler
returnValues := handler.method.Call(vals)
if rerr := returnValues[0].Interface(); rerr != nil {
err = rerr.(error)
}
return err
}
// wrap with subscriber wrappers
for i := len(router.subWrappers); i > 0; i-- {
fn = router.subWrappers[i-1](fn)
}
// create new rpc message
rpcMsg := &rpcMessage{
topic: msg.Topic(),
contentType: msg.ContentType(),
payload: req.Interface(),
codec: msg.(*rpcMessage).codec,
header: msg.Header(),
body: msg.Body(),
}
// execute the message handler
if err = fn(ctx, rpcMsg); err != nil {
errResults = append(errResults, err.Error())
}
}
}
// if no errors just return
if len(errResults) > 0 {
err = merrors.InternalServerError("go.micro.server", "subscriber error: %v", strings.Join(errResults, "\n"))
}
return err
}
+867
View File
@@ -0,0 +1,867 @@
package server
import (
"context"
"io"
"net"
"runtime/debug"
"sort"
"strconv"
"strings"
"sync"
"time"
"github.com/pkg/errors"
"go-micro.dev/v4/broker"
"go-micro.dev/v4/codec"
log "go-micro.dev/v4/logger"
"go-micro.dev/v4/metadata"
"go-micro.dev/v4/registry"
"go-micro.dev/v4/transport"
"go-micro.dev/v4/transport/headers"
"go-micro.dev/v4/util/addr"
"go-micro.dev/v4/util/backoff"
mnet "go-micro.dev/v4/util/net"
"go-micro.dev/v4/util/socket"
)
type rpcServer struct {
opts Options
// Subscribe to service name
subscriber broker.Subscriber
// Goal:
// router Router
router *router
exit chan chan error
handlers map[string]Handler
subscribers map[Subscriber][]broker.Subscriber
// Graceful exit
wg *sync.WaitGroup
// Cached service
rsvc *registry.Service
sync.RWMutex
// Marks the serve as started
started bool
// Used for first registration
registered bool
}
// NewRPCServer will create a new default RPC server.
func NewRPCServer(opts ...Option) Server {
options := NewOptions(opts...)
router := newRpcRouter()
router.hdlrWrappers = options.HdlrWrappers
router.subWrappers = options.SubWrappers
return &rpcServer{
opts: options,
router: router,
handlers: make(map[string]Handler),
subscribers: make(map[Subscriber][]broker.Subscriber),
exit: make(chan chan error),
wg: wait(options.Context),
}
}
func (s *rpcServer) Init(opts ...Option) error {
s.Lock()
defer s.Unlock()
for _, opt := range opts {
opt(&s.opts)
}
// update router if its the default
if s.opts.Router == nil {
r := newRpcRouter()
r.hdlrWrappers = s.opts.HdlrWrappers
r.serviceMap = s.router.serviceMap
r.subWrappers = s.opts.SubWrappers
s.router = r
}
s.rsvc = nil
return nil
}
// ServeConn serves a single connection.
func (s *rpcServer) ServeConn(sock transport.Socket) {
logger := s.opts.Logger
// Global error tracking
var gerr error
// Keep track of Connection: close header
var closeConn bool
// Streams are multiplexed on Micro-Stream or Micro-Id header
pool := socket.NewPool()
// Waitgroup to wait for processing to finish
// A double waitgroup is used to block the global waitgroup incase it is
// empty, but only wait for the local routines to finish with the local waitgroup.
wg := NewWaitGroup(s.getWg())
defer func() {
// Only wait if there's no error
if gerr != nil {
select {
case <-s.exit:
default:
// EOF is expected if the client closes the connection
if !errors.Is(gerr, io.EOF) {
logger.Logf(log.ErrorLevel, "error while serving connection: %v", gerr)
}
}
} else {
wg.Wait()
}
// Close all the sockets for this connection
pool.Close()
// Close underlying socket
if err := sock.Close(); err != nil {
logger.Logf(log.ErrorLevel, "failed to close socket: %v", err)
}
// recover any panics
if r := recover(); r != nil {
logger.Log(log.ErrorLevel, "panic recovered: ", r)
logger.Log(log.ErrorLevel, string(debug.Stack()))
}
}()
for {
msg := transport.Message{
Header: make(map[string]string),
}
// Close connection if Connection: close header was set
if closeConn {
return
}
// Process inbound messages one at a time
if err := sock.Recv(&msg); err != nil {
// Set a global error and return.
// We're saying we essentially can't
// use the socket anymore
gerr = errors.Wrapf(err, "%s-%s | %s", s.opts.Name, s.opts.Id, sock.Remote())
return
}
// Keep track of when to close the connection
if c := msg.Header["Connection"]; c == "close" {
closeConn = true
}
// Check the message header for micro message header, if so handle
// as micro event
if t := msg.Header[headers.Message]; len(t) > 0 {
// Process the event
ev := newEvent(msg)
if err := s.HandleEvent(ev); err != nil {
msg.Header[headers.Error] = err.Error()
logger.Logf(log.ErrorLevel, "failed to handle event: %v", err)
}
// Write back some 200
if err := sock.Send(&transport.Message{Header: msg.Header}); err != nil {
gerr = err
break
}
continue
}
// business as usual
// use Micro-Stream as the stream identifier
// in the event its blank we'll always process
// on the same socket
var (
stream bool
id string
)
if s := getHeader(headers.Stream, msg.Header); len(s) > 0 {
id = s
stream = true
} else {
// If there's no stream id then its a standard request
// use the Micro-Id
id = msg.Header[headers.ID]
}
// Check if we have an existing socket
psock, ok := pool.Get(id)
// If we don't have a socket and its a stream
// Check if its a last stream EOS error
if !ok && stream && msg.Header[headers.Error] == errLastStreamResponse.Error() {
pool.Release(psock)
continue
}
// Got an existing socket already
if ok {
// we're starting processing
wg.Add(1)
// Pass the message to that existing socket
if err := psock.Accept(&msg); err != nil {
// Release the socket if there's an error
pool.Release(psock)
}
wg.Done()
continue
}
// No socket was found so its new
// Set the local and remote values
psock.SetLocal(sock.Local())
psock.SetRemote(sock.Remote())
// Load the socket with the current message
if err := psock.Accept(&msg); err != nil {
logger.Logf(log.ErrorLevel, "Socket failed to accept message: %v", err)
}
// Now walk the usual path
// We use this Timeout header to set a server deadline
to := msg.Header["Timeout"]
// We use this Content-Type header to identify the codec needed
contentType := msg.Header["Content-Type"]
// Copy the message headers
header := make(map[string]string, len(msg.Header))
for k, v := range msg.Header {
header[k] = v
}
// Set local/remote ips
header["Local"] = sock.Local()
header["Remote"] = sock.Remote()
// Create new context with the metadata
ctx := metadata.NewContext(context.Background(), header)
// Set the timeout from the header if we have it
if len(to) > 0 {
if n, err := strconv.ParseUint(to, 10, 64); err == nil {
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, time.Duration(n))
defer cancel()
}
}
// If there's no content type default it
if len(contentType) == 0 {
msg.Header["Content-Type"] = DefaultContentType
contentType = DefaultContentType
}
// Setup old protocol
cf := setupProtocol(&msg)
// No legacy codec needed
if cf == nil {
var err error
// Try get a new codec
if cf, err = s.newCodec(contentType); err != nil {
// No codec found so send back an error
if err = sock.Send(&transport.Message{
Header: map[string]string{
"Content-Type": "text/plain",
},
Body: []byte(err.Error()),
}); err != nil {
gerr = err
}
pool.Release(psock)
continue
}
}
// Create a new rpc codec based on the pseudo socket and codec
rcodec := newRPCCodec(&msg, psock, cf)
// Check the protocol as well
protocol := rcodec.String()
// Internal request
request := rpcRequest{
service: getHeader(headers.Request, msg.Header),
method: getHeader(headers.Method, msg.Header),
endpoint: getHeader(headers.Endpoint, msg.Header),
contentType: contentType,
codec: rcodec,
header: msg.Header,
body: msg.Body,
socket: psock,
stream: stream,
}
// Internal response
response := rpcResponse{
header: make(map[string]string),
socket: psock,
codec: rcodec,
}
// Wait for two coroutines to exit
// Serve the request and process the outbound messages
wg.Add(2)
// Process the outbound messages from the socket
go func(psock *socket.Socket) {
defer func() {
// TODO: don't hack this but if its grpc just break out of the stream
// We do this because the underlying connection is h2 and its a stream
if protocol == "grpc" {
if err := sock.Close(); err != nil {
logger.Logf(log.ErrorLevel, "Failed to close socket: %v", err)
}
}
s.deferer(pool, psock, wg)
}()
for {
// Get the message from our internal handler/stream
m := new(transport.Message)
if err := psock.Process(m); err != nil {
return
}
// Send the message back over the socket
if err := sock.Send(m); err != nil {
return
}
}
}(psock)
// Serve the request in a go routine as this may be a stream
go func(psock *socket.Socket) {
defer s.deferer(pool, psock, wg)
s.serveReq(ctx, msg, &request, &response, rcodec)
}(psock)
}
}
func (s *rpcServer) NewHandler(h interface{}, opts ...HandlerOption) Handler {
return s.router.NewHandler(h, opts...)
}
func (s *rpcServer) Handle(h Handler) error {
s.Lock()
defer s.Unlock()
if err := s.router.Handle(h); err != nil {
return err
}
s.handlers[h.Name()] = h
return nil
}
func (s *rpcServer) Register() error {
config := s.Options()
logger := config.Logger
// Registry function used to register the service
regFunc := s.newRegFuc(config)
// Directly register if service was cached
rsvc := s.getCachedService()
if rsvc != nil {
if err := regFunc(rsvc); err != nil {
return errors.Wrap(err, "failed to register service")
}
return nil
}
// Only cache service if host IP valid
addr, cacheService, err := s.getAddr(config)
if err != nil {
return err
}
node := &registry.Node{
// TODO: node id should be set better. Add native option to specify
// host id through either config or ENV. Also look at logging of name.
Id: config.Name + "-" + config.Id,
Address: addr,
Metadata: s.newNodeMetedata(config),
}
service := &registry.Service{
Name: config.Name,
Version: config.Version,
Nodes: []*registry.Node{node},
Endpoints: s.getEndpoints(),
}
registered := s.isRegistered()
if !registered {
logger.Logf(log.InfoLevel, "Registry [%s] Registering node: %s", config.Registry.String(), node.Id)
}
// Register the service
if err := regFunc(service); err != nil {
return errors.Wrap(err, "failed to register service")
}
// Already registered? don't need to register subscribers
if registered {
return nil
}
s.Lock()
defer s.Unlock()
s.registered = true
// Cache service
if cacheService {
s.rsvc = service
}
// Set what we're advertising
s.opts.Advertise = addr
// Router can exchange messages on broker
// Subscribe to the topic with its own name
if err := s.subscribeServer(config); err != nil {
return errors.Wrap(err, "failed to subscribe to service name topic")
}
// Subscribe for all of the subscribers
if err := s.reSubscribe(config); err != nil {
return errors.Wrap(err, "failed to resubscribe")
}
return nil
}
func (s *rpcServer) Deregister() error {
config := s.Options()
logger := config.Logger
addr, _, err := s.getAddr(config)
if err != nil {
return err
}
// TODO: there should be a better way to do this than reconstruct the service
// Edge case is that if service is not cached
node := &registry.Node{
// TODO: also update node id naming
Id: config.Name + "-" + config.Id,
Address: addr,
}
service := &registry.Service{
Name: config.Name,
Version: config.Version,
Nodes: []*registry.Node{node},
}
logger.Logf(log.InfoLevel, "Registry [%s] Deregistering node: %s", config.Registry.String(), node.Id)
if err := config.Registry.Deregister(service); err != nil {
return err
}
s.Lock()
defer s.Unlock()
s.rsvc = nil
if !s.registered {
return nil
}
s.registered = false
// close the subscriber
if s.subscriber != nil {
if err := s.subscriber.Unsubscribe(); err != nil {
logger.Logf(log.ErrorLevel, "Failed to unsubscribe service from service name topic: %v", err)
}
s.subscriber = nil
}
for sb, subs := range s.subscribers {
for i, sub := range subs {
logger.Logf(log.InfoLevel, "Unsubscribing %s from topic: %s", node.Id, sub.Topic())
if err := sub.Unsubscribe(); err != nil {
logger.Logf(log.ErrorLevel, "Failed to unsubscribe subscriber nr. %d from topic %s: %v", i+1, sub.Topic(), err)
}
}
s.subscribers[sb] = nil
}
return nil
}
func (s *rpcServer) Start() error {
if s.isStarted() {
return nil
}
config := s.Options()
logger := config.Logger
// start listening on the listener
listener, err := config.Transport.Listen(config.Address, config.ListenOptions...)
if err != nil {
return err
}
logger.Logf(log.InfoLevel, "Transport [%s] Listening on %s", config.Transport.String(), listener.Addr())
// swap address
addr := s.swapAddr(config, listener.Addr())
// connect to the broker
brokerName := config.Broker.String()
if err = config.Broker.Connect(); err != nil {
logger.Logf(log.ErrorLevel, "Broker [%s] connect error: %v", brokerName, err)
return err
}
logger.Logf(log.InfoLevel, "Broker [%s] Connected to %s", brokerName, config.Broker.Address())
// Use RegisterCheck func before register
if err = s.opts.RegisterCheck(s.opts.Context); err != nil {
logger.Logf(log.ErrorLevel, "Server %s-%s register check error: %s", config.Name, config.Id, err)
} else if err = s.Register(); err != nil {
// Perform initial registration
logger.Logf(log.ErrorLevel, "Server %s-%s register error: %s", config.Name, config.Id, err)
}
exit := make(chan bool)
// Listen for connections
go s.listen(listener, exit)
// Keep the service registered to registry
go s.registrar(listener, addr, config, exit)
s.setStarted(true)
return nil
}
func (s *rpcServer) Stop() error {
if !s.isStarted() {
return nil
}
ch := make(chan error)
s.exit <- ch
err := <-ch
s.setStarted(false)
return err
}
func (s *rpcServer) String() string {
return "mucp"
}
// newRegFuc will create a new registry function used to register the service.
func (s *rpcServer) newRegFuc(config Options) func(service *registry.Service) error {
return func(service *registry.Service) error {
rOpts := []registry.RegisterOption{registry.RegisterTTL(config.RegisterTTL)}
var regErr error
// Attempt to register. If registration fails, back off and try again.
// TODO: see if we can improve the retry mechanism. Maybe retry lib, maybe config values
for i := 0; i < 3; i++ {
if err := config.Registry.Register(service, rOpts...); err != nil {
regErr = err
time.Sleep(backoff.Do(i + 1))
continue
}
return nil
}
return regErr
}
}
// getAddr will take the advertise or service address, and return it.
func (s *rpcServer) getAddr(config Options) (string, bool, error) {
// Use advertise address if provided, else use service address
advt := config.Address
if len(config.Advertise) > 0 {
advt = config.Advertise
}
// Use explicit host and port if possible
host, port := advt, ""
if cnt := strings.Count(advt, ":"); cnt >= 1 {
// ipv6 address in format [host]:port or ipv4 host:port
h, p, err := net.SplitHostPort(advt)
if err != nil {
return "", false, err
}
host, port = h, p
}
validHost := net.ParseIP(host) != nil
addr, err := addr.Extract(host)
if err != nil {
return "", false, err
}
// mq-rpc(eg. nats) doesn't need the port. its addr is queue name.
if port != "" {
addr = mnet.HostPort(addr, port)
}
return addr, validHost, nil
}
// newNodeMetedata creates a new metadata map with default values.
func (s *rpcServer) newNodeMetedata(config Options) metadata.Metadata {
md := metadata.Copy(config.Metadata)
// TODO: revisit this for v5
md["transport"] = config.Transport.String()
md["broker"] = config.Broker.String()
md["server"] = s.String()
md["registry"] = config.Registry.String()
md["protocol"] = "mucp"
return md
}
// getEndpoints takes the list of handlers and subscribers and adds them to
// a single endpoints list.
func (s *rpcServer) getEndpoints() []*registry.Endpoint {
s.RLock()
defer s.RUnlock()
var handlerList []string
for n, e := range s.handlers {
// Only advertise non internal handlers
if !e.Options().Internal {
handlerList = append(handlerList, n)
}
}
// Maps are ordered randomly, sort the keys for consistency
// TODO: replace with generic version
sort.Strings(handlerList)
var subscriberList []Subscriber
for e := range s.subscribers {
// Only advertise non internal subscribers
if !e.Options().Internal {
subscriberList = append(subscriberList, e)
}
}
sort.Slice(subscriberList, func(i, j int) bool {
return subscriberList[i].Topic() > subscriberList[j].Topic()
})
endpoints := make([]*registry.Endpoint, 0, len(handlerList)+len(subscriberList))
for _, n := range handlerList {
endpoints = append(endpoints, s.handlers[n].Endpoints()...)
}
for _, e := range subscriberList {
endpoints = append(endpoints, e.Endpoints()...)
}
return endpoints
}
func (s *rpcServer) listen(listener transport.Listener, exit chan bool) {
for {
// Start listening for connections
// This will block until either exit signal given or error occurred
err := listener.Accept(s.ServeConn)
// TODO: listen for messages
// msg := broker.Exchange(service).Consume()
select {
// check if we're supposed to exit
case <-exit:
return
// check the error and backoff
default:
if err != nil {
s.opts.Logger.Logf(log.ErrorLevel, "Accept error: %v", err)
time.Sleep(time.Second)
continue
}
}
return
}
}
// registrar is responsible for keeping the service registered to the registry.
func (s *rpcServer) registrar(listener transport.Listener, addr string, config Options, exit chan bool) {
logger := config.Logger
// Only process if it exists
ticker := new(time.Ticker)
if s.opts.RegisterInterval > time.Duration(0) {
ticker = time.NewTicker(s.opts.RegisterInterval)
}
// Return error chan
var ch chan error
Loop:
for {
select {
// Register self on interval
case <-ticker.C:
registered := s.isRegistered()
rerr := s.opts.RegisterCheck(s.opts.Context)
if rerr != nil && registered {
logger.Logf(log.ErrorLevel, "Server %s-%s register check error: %s, deregister it", config.Name, config.Id, rerr)
// deregister self in case of error
if err := s.Deregister(); err != nil {
logger.Logf(log.ErrorLevel, "Server %s-%s deregister error: %s", config.Name, config.Id, err)
}
} else if rerr != nil && !registered {
logger.Logf(log.ErrorLevel, "Server %s-%s register check error: %s", config.Name, config.Id, rerr)
continue
}
if err := s.Register(); err != nil {
logger.Logf(log.ErrorLevel, "Server %s-%s register error: %s", config.Name, config.Id, err)
}
// Wait for exit signal
case ch = <-s.exit:
ticker.Stop()
close(exit)
break Loop
}
}
// Shutting down, deregister
if s.isRegistered() {
if err := s.Deregister(); err != nil {
logger.Logf(log.ErrorLevel, "Server %s-%s deregister error: %s", config.Name, config.Id, err)
}
}
// Wait for requests to finish
if swg := s.getWg(); swg != nil {
swg.Wait()
}
// Close transport listener
ch <- listener.Close()
brokerName := config.Broker.String()
logger.Logf(log.InfoLevel, "Broker [%s] Disconnected from %s", brokerName, config.Broker.Address())
// Disconnect the broker
if err := config.Broker.Disconnect(); err != nil {
logger.Logf(log.ErrorLevel, "Broker [%s] Disconnect error: %v", brokerName, err)
}
// Swap back address
s.setOptsAddr(addr)
}
func (s *rpcServer) serveReq(ctx context.Context, msg transport.Message, req *rpcRequest, resp *rpcResponse, rcodec codec.Codec) {
logger := s.opts.Logger
router := s.getRouter()
// serve the actual request using the request router
if serveRequestError := router.ServeRequest(ctx, req, resp); serveRequestError != nil {
// write an error response
writeError := rcodec.Write(&codec.Message{
Header: msg.Header,
Error: serveRequestError.Error(),
Type: codec.Error,
}, nil)
// if the server request is an EOS error we let the socket know
// sometimes the socket is already closed on the other side, so we can ignore that error
alreadyClosed := errors.Is(serveRequestError, errLastStreamResponse) && errors.Is(writeError, io.EOF)
// could not write error response
if writeError != nil && !alreadyClosed {
logger.Logf(log.DebugLevel, "rpc: unable to write error response: %v", writeError)
}
}
}
func (s *rpcServer) deferer(pool *socket.Pool, psock *socket.Socket, wg *waitGroup) {
pool.Release(psock)
wg.Done()
logger := s.opts.Logger
if r := recover(); r != nil {
logger.Log(log.ErrorLevel, "panic recovered: ", r)
logger.Log(log.ErrorLevel, string(debug.Stack()))
}
}
func (s *rpcServer) getRouter() Router {
router := Router(s.router)
// if not nil use the router specified
if s.opts.Router != nil {
// create a wrapped function
handler := func(ctx context.Context, req Request, rsp interface{}) error {
return s.opts.Router.ServeRequest(ctx, req, rsp.(Response))
}
// execute the wrapper for it
for i := len(s.opts.HdlrWrappers); i > 0; i-- {
handler = s.opts.HdlrWrappers[i-1](handler)
}
// set the router
router = rpcRouter{h: handler}
}
return router
}
+104
View File
@@ -0,0 +1,104 @@
package server
import (
"context"
"errors"
"io"
"sync"
"go-micro.dev/v4/codec"
)
// Implements the Streamer interface.
type rpcStream struct {
err error
request Request
codec codec.Codec
context context.Context
id string
sync.RWMutex
closed bool
}
func (r *rpcStream) Context() context.Context {
return r.context
}
func (r *rpcStream) Request() Request {
return r.request
}
func (r *rpcStream) Send(msg interface{}) error {
r.Lock()
defer r.Unlock()
resp := codec.Message{
Target: r.request.Service(),
Method: r.request.Method(),
Endpoint: r.request.Endpoint(),
Id: r.id,
Type: codec.Response,
}
if err := r.codec.Write(&resp, msg); err != nil {
r.err = err
}
return nil
}
func (r *rpcStream) Recv(msg interface{}) error {
req := new(codec.Message)
req.Type = codec.Request
err := r.codec.ReadHeader(req, req.Type)
r.Lock()
defer r.Unlock()
if err != nil {
// discard body
r.codec.ReadBody(nil)
r.err = err
return err
}
// check the error
if len(req.Error) > 0 {
// Check the client closed the stream
switch req.Error {
case errLastStreamResponse.Error():
// discard body
r.Unlock()
r.codec.ReadBody(nil)
r.Lock()
r.err = io.EOF
return io.EOF
default:
return errors.New(req.Error)
}
}
// we need to stay up to date with sequence numbers
r.id = req.Id
r.Unlock()
err = r.codec.ReadBody(msg)
r.Lock()
if err != nil {
r.err = err
return err
}
return nil
}
func (r *rpcStream) Error() error {
r.RLock()
defer r.RUnlock()
return r.err
}
func (r *rpcStream) Close() error {
r.Lock()
defer r.Unlock()
r.closed = true
return r.codec.Close()
}
+39
View File
@@ -0,0 +1,39 @@
package server
import (
"sync"
)
// waitgroup for global management of connections.
type waitGroup struct {
// global waitgroup
gg *sync.WaitGroup
// local waitgroup
lg sync.WaitGroup
}
// NewWaitGroup returns a new double waitgroup for global management of processes.
func NewWaitGroup(gWg *sync.WaitGroup) *waitGroup {
return &waitGroup{
gg: gWg,
}
}
func (w *waitGroup) Add(i int) {
w.lg.Add(i)
if w.gg != nil {
w.gg.Add(i)
}
}
func (w *waitGroup) Done() {
w.lg.Done()
if w.gg != nil {
w.gg.Done()
}
}
func (w *waitGroup) Wait() {
// only wait on local group
w.lg.Wait()
}
+232
View File
@@ -0,0 +1,232 @@
// Package server is an interface for a micro server
package server
import (
"context"
"os"
"os/signal"
"time"
"github.com/google/uuid"
"go-micro.dev/v4/codec"
log "go-micro.dev/v4/logger"
"go-micro.dev/v4/registry"
signalutil "go-micro.dev/v4/util/signal"
)
// Server is a simple micro server abstraction.
type Server interface {
// Initialize options
Init(...Option) error
// Retrieve the options
Options() Options
// Register a handler
Handle(Handler) error
// Create a new handler
NewHandler(interface{}, ...HandlerOption) Handler
// Create a new subscriber
NewSubscriber(string, interface{}, ...SubscriberOption) Subscriber
// Register a subscriber
Subscribe(Subscriber) error
// Start the server
Start() error
// Stop the server
Stop() error
// Server implementation
String() string
}
// Router handle serving messages.
type Router interface {
// ProcessMessage processes a message
ProcessMessage(context.Context, Message) error
// ServeRequest processes a request to completion
ServeRequest(context.Context, Request, Response) error
}
// Message is an async message interface.
type Message interface {
// Topic of the message
Topic() string
// The decoded payload value
Payload() interface{}
// The content type of the payload
ContentType() string
// The raw headers of the message
Header() map[string]string
// The raw body of the message
Body() []byte
// Codec used to decode the message
Codec() codec.Reader
}
// Request is a synchronous request interface.
type Request interface {
// Service name requested
Service() string
// The action requested
Method() string
// Endpoint name requested
Endpoint() string
// Content type provided
ContentType() string
// Header of the request
Header() map[string]string
// Body is the initial decoded value
Body() interface{}
// Read the undecoded request body
Read() ([]byte, error)
// The encoded message stream
Codec() codec.Reader
// Indicates whether its a stream
Stream() bool
}
// Response is the response writer for unencoded messages.
type Response interface {
// Encoded writer
Codec() codec.Writer
// Write the header
WriteHeader(map[string]string)
// write a response directly to the client
Write([]byte) error
}
// Stream represents a stream established with a client.
// A stream can be bidirectional which is indicated by the request.
// The last error will be left in Error().
// EOF indicates end of the stream.
type Stream interface {
Context() context.Context
Request() Request
Send(interface{}) error
Recv(interface{}) error
Error() error
Close() error
}
// Handler interface represents a request handler. It's generated
// by passing any type of public concrete object with endpoints into server.NewHandler.
// Most will pass in a struct.
//
// Example:
//
// type Greeter struct {}
//
// func (g *Greeter) Hello(context, request, response) error {
// return nil
// }
type Handler interface {
Name() string
Handler() interface{}
Endpoints() []*registry.Endpoint
Options() HandlerOptions
}
// Subscriber interface represents a subscription to a given topic using
// a specific subscriber function or object with endpoints. It mirrors
// the handler in its behavior.
type Subscriber interface {
Topic() string
Subscriber() interface{}
Endpoints() []*registry.Endpoint
Options() SubscriberOptions
}
type Option func(*Options)
var (
DefaultAddress = ":0"
DefaultName = "go.micro.server"
DefaultVersion = "latest"
DefaultId = uuid.New().String()
DefaultServer Server = NewRPCServer()
DefaultRouter = newRpcRouter()
DefaultRegisterCheck = func(context.Context) error { return nil }
DefaultRegisterInterval = time.Second * 30
DefaultRegisterTTL = time.Second * 90
// NewServer creates a new server.
NewServer func(...Option) Server = NewRPCServer
)
// DefaultOptions returns config options for the default service.
func DefaultOptions() Options {
return DefaultServer.Options()
}
func Init(opt ...Option) {
if DefaultServer == nil {
DefaultServer = NewRPCServer(opt...)
}
DefaultServer.Init(opt...)
}
// NewRouter returns a new router.
func NewRouter() *router {
return newRpcRouter()
}
// NewSubscriber creates a new subscriber interface with the given topic
// and handler using the default server.
func NewSubscriber(topic string, h interface{}, opts ...SubscriberOption) Subscriber {
return DefaultServer.NewSubscriber(topic, h, opts...)
}
// NewHandler creates a new handler interface using the default server
// Handlers are required to be a public object with public
// endpoints. Call to a service endpoint such as Foo.Bar expects
// the type:
//
// type Foo struct {}
// func (f *Foo) Bar(ctx, req, rsp) error {
// return nil
// }
func NewHandler(h interface{}, opts ...HandlerOption) Handler {
return DefaultServer.NewHandler(h, opts...)
}
// Handle registers a handler interface with the default server to
// handle inbound requests.
func Handle(h Handler) error {
return DefaultServer.Handle(h)
}
// Subscribe registers a subscriber interface with the default server
// which subscribes to specified topic with the broker.
func Subscribe(s Subscriber) error {
return DefaultServer.Subscribe(s)
}
// Run starts the default server and waits for a kill
// signal before exiting. Also registers/deregisters the server.
func Run() error {
if err := Start(); err != nil {
return err
}
ch := make(chan os.Signal, 1)
signal.Notify(ch, signalutil.Shutdown()...)
DefaultServer.Options().Logger.Logf(log.InfoLevel, "Received signal %s", <-ch)
return Stop()
}
// Start starts the default server.
func Start() error {
config := DefaultServer.Options()
config.Logger.Logf(log.InfoLevel, "Starting server %s id %s", config.Name, config.Id)
return DefaultServer.Start()
}
// Stop stops the default server.
func Stop() error {
DefaultServer.Options().Logger.Logf(log.InfoLevel, "Stopping server")
return DefaultServer.Stop()
}
// String returns name of Server implementation.
func String() string {
return DefaultServer.String()
}
+175
View File
@@ -0,0 +1,175 @@
package server
import (
"fmt"
"reflect"
"go-micro.dev/v4/registry"
)
const (
subSig = "func(context.Context, interface{}) error"
)
type handler struct {
reqType reflect.Type
ctxType reflect.Type
method reflect.Value
}
type subscriber struct {
opts SubscriberOptions
typ reflect.Type
subscriber interface{}
rcvr reflect.Value
topic string
handlers []*handler
endpoints []*registry.Endpoint
}
func newSubscriber(topic string, sub interface{}, opts ...SubscriberOption) Subscriber {
options := SubscriberOptions{
AutoAck: true,
}
for _, o := range opts {
o(&options)
}
var endpoints []*registry.Endpoint
var handlers []*handler
if typ := reflect.TypeOf(sub); typ.Kind() == reflect.Func {
h := &handler{
method: reflect.ValueOf(sub),
}
switch typ.NumIn() {
case 1:
h.reqType = typ.In(0)
case 2:
h.ctxType = typ.In(0)
h.reqType = typ.In(1)
}
handlers = append(handlers, h)
endpoints = append(endpoints, &registry.Endpoint{
Name: "Func",
Request: extractSubValue(typ),
Metadata: map[string]string{
"topic": topic,
"subscriber": "true",
},
})
} else {
hdlr := reflect.ValueOf(sub)
name := reflect.Indirect(hdlr).Type().Name()
for m := 0; m < typ.NumMethod(); m++ {
method := typ.Method(m)
h := &handler{
method: method.Func,
}
switch method.Type.NumIn() {
case 2:
h.reqType = method.Type.In(1)
case 3:
h.ctxType = method.Type.In(1)
h.reqType = method.Type.In(2)
}
handlers = append(handlers, h)
endpoints = append(endpoints, &registry.Endpoint{
Name: name + "." + method.Name,
Request: extractSubValue(method.Type),
Metadata: map[string]string{
"topic": topic,
"subscriber": "true",
},
})
}
}
return &subscriber{
rcvr: reflect.ValueOf(sub),
typ: reflect.TypeOf(sub),
topic: topic,
subscriber: sub,
handlers: handlers,
endpoints: endpoints,
opts: options,
}
}
func validateSubscriber(sub Subscriber) error {
typ := reflect.TypeOf(sub.Subscriber())
var argType reflect.Type
if typ.Kind() == reflect.Func {
name := "Func"
switch typ.NumIn() {
case 2:
argType = typ.In(1)
default:
return fmt.Errorf("subscriber %v takes wrong number of args: %v required signature %s", name, typ.NumIn(), subSig)
}
if !isExportedOrBuiltinType(argType) {
return fmt.Errorf("subscriber %v argument type not exported: %v", name, argType)
}
if typ.NumOut() != 1 {
return fmt.Errorf("subscriber %v has wrong number of outs: %v require signature %s",
name, typ.NumOut(), subSig)
}
if returnType := typ.Out(0); returnType != typeOfError {
return fmt.Errorf("subscriber %v returns %v not error", name, returnType.String())
}
} else {
hdlr := reflect.ValueOf(sub.Subscriber())
name := reflect.Indirect(hdlr).Type().Name()
for m := 0; m < typ.NumMethod(); m++ {
method := typ.Method(m)
switch method.Type.NumIn() {
case 3:
argType = method.Type.In(2)
default:
return fmt.Errorf("subscriber %v.%v takes wrong number of args: %v required signature %s",
name, method.Name, method.Type.NumIn(), subSig)
}
if !isExportedOrBuiltinType(argType) {
return fmt.Errorf("%v argument type not exported: %v", name, argType)
}
if method.Type.NumOut() != 1 {
return fmt.Errorf(
"subscriber %v.%v has wrong number of outs: %v require signature %s",
name, method.Name, method.Type.NumOut(), subSig)
}
if returnType := method.Type.Out(0); returnType != typeOfError {
return fmt.Errorf("subscriber %v.%v returns %v not error", name, method.Name, returnType.String())
}
}
}
return nil
}
func (s *subscriber) Topic() string {
return s.topic
}
func (s *subscriber) Subscriber() interface{} {
return s.subscriber
}
func (s *subscriber) Endpoints() []*registry.Endpoint {
return s.endpoints
}
func (s *subscriber) Options() SubscriberOptions {
return s.opts
}
+27
View File
@@ -0,0 +1,27 @@
package server
import (
"context"
)
// HandlerFunc represents a single method of a handler. It's used primarily
// for the wrappers. What's handed to the actual method is the concrete
// request and response types.
type HandlerFunc func(ctx context.Context, req Request, rsp interface{}) error
// SubscriberFunc represents a single method of a subscriber. It's used primarily
// for the wrappers. What's handed to the actual method is the concrete
// publication message.
type SubscriberFunc func(ctx context.Context, msg Message) error
// HandlerWrapper wraps the HandlerFunc and returns the equivalent.
type HandlerWrapper func(HandlerFunc) HandlerFunc
// SubscriberWrapper wraps the SubscriberFunc and returns the equivalent.
type SubscriberWrapper func(SubscriberFunc) SubscriberFunc
// StreamWrapper wraps a Stream interface and returns the equivalent.
// Because streams exist for the lifetime of a method invocation this
// is a convenient way to wrap a Stream as its in use for trace, monitoring,
// metrics, etc.
type StreamWrapper func(Stream) Stream