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
424 changed files with 6383 additions and 22490 deletions

128
server/mucp/extractor.go Normal file
View File

@@ -0,0 +1,128 @@
package mucp
import (
"fmt"
"reflect"
"strings"
"github.com/micro/go-micro/v3/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)
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)
}

View File

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

View File

@@ -2,10 +2,14 @@
package mucp
import (
"github.com/micro/go-micro/v2/server"
"github.com/micro/go-micro/v3/server"
)
var (
DefaultRouter = newRpcRouter()
)
// NewServer returns a micro server interface
func NewServer(opts ...server.Option) server.Server {
return server.NewServer(opts...)
return newServer(opts...)
}

56
server/mucp/options.go Normal file
View File

@@ -0,0 +1,56 @@
package mucp
import (
"github.com/micro/go-micro/v3/broker/http"
"github.com/micro/go-micro/v3/codec"
"github.com/micro/go-micro/v3/registry/mdns"
"github.com/micro/go-micro/v3/server"
thttp "github.com/micro/go-micro/v3/transport/http"
)
func newOptions(opt ...server.Option) server.Options {
opts := server.Options{
Codecs: make(map[string]codec.NewCodec),
Metadata: map[string]string{},
RegisterInterval: server.DefaultRegisterInterval,
RegisterTTL: server.DefaultRegisterTTL,
}
for _, o := range opt {
o(&opts)
}
if opts.Broker == nil {
opts.Broker = http.NewBroker()
}
if opts.Registry == nil {
opts.Registry = mdns.NewRegistry()
}
if opts.Transport == nil {
opts.Transport = thttp.NewTransport()
}
if opts.RegisterCheck == nil {
opts.RegisterCheck = server.DefaultRegisterCheck
}
if len(opts.Address) == 0 {
opts.Address = server.DefaultAddress
}
if len(opts.Name) == 0 {
opts.Name = server.DefaultName
}
if len(opts.Id) == 0 {
opts.Id = server.DefaultId
}
if len(opts.Version) == 0 {
opts.Version = server.DefaultVersion
}
return opts
}

354
server/mucp/rpc_codec.go Normal file
View File

@@ -0,0 +1,354 @@
package mucp
import (
"bytes"
"sync"
"github.com/micro/go-micro/v3/codec"
raw "github.com/micro/go-micro/v3/codec/bytes"
"github.com/micro/go-micro/v3/codec/grpc"
"github.com/micro/go-micro/v3/codec/json"
"github.com/micro/go-micro/v3/codec/jsonrpc"
"github.com/micro/go-micro/v3/codec/proto"
"github.com/micro/go-micro/v3/codec/protorpc"
"github.com/micro/go-micro/v3/transport"
"github.com/oxtoacart/bpool"
"github.com/pkg/errors"
)
type rpcCodec struct {
socket transport.Socket
codec codec.Codec
protocol string
req *transport.Message
buf *readWriteCloser
// check if we're the first
sync.RWMutex
first chan bool
}
type readWriteCloser struct {
sync.RWMutex
wbuf *bytes.Buffer
rbuf *bytes.Buffer
}
var (
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, "Micro-Id")
m.Error = set(m.Error, "Micro-Error")
m.Endpoint = set(m.Endpoint, "Micro-Endpoint")
m.Method = set(m.Method, "Micro-Method")
m.Target = set(m.Target, "Micro-Service")
// 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("Micro-Id", r.Id)
set("Micro-Service", r.Target)
set("Micro-Method", r.Method)
set("Micro-Endpoint", r.Endpoint)
set("Micro-Error", r.Error)
}
// setupProtocol sets up the old protocol
func setupProtocol(msg *transport.Message) codec.NewCodec {
service := getHeader("Micro-Service", msg.Header)
method := getHeader("Micro-Method", msg.Header)
endpoint := getHeader("Micro-Endpoint", msg.Header)
protocol := getHeader("Micro-Protocol", msg.Header)
target := getHeader("Micro-Target", msg.Header)
topic := getHeader("Micro-Topic", 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["Micro-Method"] = endpoint
}
// no endpoint then set to method
if len(endpoint) == 0 {
msg.Header["Micro-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)
// set the protocol
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
m := 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
m.Header = tm.Header
// set the message body
m.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(&m)
// read header via codec
if err := c.codec.ReadHeader(&m, codec.Request); err != nil {
return err
}
// fallback for 0.14 and older
if len(m.Endpoint) == 0 {
m.Endpoint = m.Method
}
// set message
*r = m
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["Micro-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
}

View File

@@ -0,0 +1,109 @@
package mucp
import (
"bytes"
"errors"
"testing"
"github.com/micro/go-micro/v3/codec"
"github.com/micro/go-micro/v3/transport"
)
// testCodec is a dummy codec that only knows how to encode nil bodies
type testCodec struct {
buf *bytes.Buffer
}
type testSocket struct {
local string
remote string
}
// TestCodecWriteError simulates what happens when a codec is unable
// to encode a message (e.g. a missing branch of an "oneof" message in
// protobufs)
//
// We expect an error to be sent to the socket. Previously the socket
// would remain open with no bytes sent, leading to client-side
// timeouts.
func TestCodecWriteError(t *testing.T) {
socket := testSocket{}
message := transport.Message{
Header: map[string]string{},
Body: []byte{},
}
rwc := readWriteCloser{
rbuf: new(bytes.Buffer),
wbuf: new(bytes.Buffer),
}
c := rpcCodec{
buf: &rwc,
codec: &testCodec{
buf: rwc.wbuf,
},
req: &message,
socket: socket,
}
err := c.Write(&codec.Message{
Endpoint: "Service.Endpoint",
Id: "0",
Error: "",
}, "body")
if err != nil {
t.Fatalf(`Expected Write to fail; got "%+v" instead`, err)
}
const expectedError = "Unable to encode body: simulating a codec write failure"
actualError := rwc.wbuf.String()
if actualError != expectedError {
t.Fatalf(`Expected error "%+v" in the write buffer, got "%+v" instead`, expectedError, actualError)
}
}
func (c *testCodec) ReadHeader(message *codec.Message, typ codec.MessageType) error {
return nil
}
func (c *testCodec) ReadBody(dest interface{}) error {
return nil
}
func (c *testCodec) Write(message *codec.Message, dest interface{}) error {
if dest != nil {
return errors.New("simulating a codec write failure")
}
c.buf.Write([]byte(message.Error))
return nil
}
func (c *testCodec) Close() error {
return nil
}
func (c *testCodec) String() string {
return "string"
}
func (s testSocket) Local() string {
return s.local
}
func (s testSocket) Remote() string {
return s.remote
}
func (s testSocket) Recv(message *transport.Message) error {
return nil
}
func (s testSocket) Send(message *transport.Message) error {
return nil
}
func (s testSocket) Close() error {
return nil
}

38
server/mucp/rpc_event.go Normal file
View File

@@ -0,0 +1,38 @@
package mucp
import (
"github.com/micro/go-micro/v3/broker"
"github.com/micro/go-micro/v3/transport"
)
// 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["Micro-Topic"]
}
func newEvent(msg transport.Message) *event {
return &event{
message: &broker.Message{
Header: msg.Header,
Body: msg.Body,
},
}
}

View File

@@ -0,0 +1,66 @@
package mucp
import (
"reflect"
"github.com/micro/go-micro/v3/registry"
"github.com/micro/go-micro/v3/server"
)
type rpcHandler struct {
name string
handler interface{}
endpoints []*registry.Endpoint
opts server.HandlerOptions
}
func newRpcHandler(handler interface{}, opts ...server.HandlerOption) server.Handler {
options := server.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() server.HandlerOptions {
return r.opts
}

107
server/mucp/rpc_request.go Normal file
View File

@@ -0,0 +1,107 @@
package mucp
import (
"bytes"
"github.com/micro/go-micro/v3/codec"
"github.com/micro/go-micro/v3/transport"
"github.com/micro/go-micro/v3/util/buf"
)
type rpcRequest struct {
service string
method string
endpoint string
contentType string
socket transport.Socket
codec codec.Codec
header map[string]string
body []byte
rawBody interface{}
stream bool
first bool
}
type rpcMessage struct {
topic string
contentType string
payload interface{}
header map[string]string
body []byte
codec codec.NewCodec
}
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)
}

View File

@@ -0,0 +1,35 @@
package mucp
import (
"net/http"
"github.com/micro/go-micro/v3/codec"
"github.com/micro/go-micro/v3/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,
})
}

617
server/mucp/rpc_router.go Normal file
View File

@@ -0,0 +1,617 @@
package mucp
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//
// Meh, we need to get rid of this shit
import (
"context"
"errors"
"fmt"
"io"
"reflect"
"runtime/debug"
"strings"
"sync"
"unicode"
"unicode/utf8"
"github.com/micro/go-micro/v3/codec"
merrors "github.com/micro/go-micro/v3/errors"
"github.com/micro/go-micro/v3/server"
)
var (
lastStreamResponseError = 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 {
sync.Mutex // protects counters
method reflect.Method
ArgType reflect.Type
ReplyType reflect.Type
ContextType reflect.Type
stream bool
}
type service struct {
name string // name of service
rcvr reflect.Value // receiver of methods for the service
typ reflect.Type // type of the receiver
method map[string]*methodType // registered methods
}
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 {
name string
mu sync.Mutex // protects the serviceMap
serviceMap map[string]*service
reqLock sync.Mutex // protects freeReq
freeReq *request
respLock sync.Mutex // protects freeResp
freeResp *response
// handler wrappers
hdlrWrappers []server.HandlerWrapper
// subscriber wrappers
subWrappers []server.SubscriberWrapper
su sync.RWMutex
subscribers map[string][]*subscriber
}
// rpcRouter encapsulates functions that become a server.Router
type rpcRouter struct {
h func(context.Context, server.Request, interface{}) error
m func(context.Context, server.Message) error
}
func (r rpcRouter) ProcessMessage(ctx context.Context, msg server.Message) error {
return r.m(ctx, msg)
}
func (r rpcRouter) ServeRequest(ctx context.Context, req server.Request, rsp server.Response) error {
return r.h(ctx, req, rsp)
}
func newRpcRouter() *router {
return &router{
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) *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:
log.Errorf("method %v of %v has wrong number of ins: %v", mname, mtype, mtype.NumIn())
return nil
}
if stream {
// check stream type
streamType := reflect.TypeOf((*server.Stream)(nil)).Elem()
if !argType.Implements(streamType) {
log.Errorf("%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) {
log.Errorf("%v argument type not exported: %v", mname, argType)
return nil
}
if replyType.Kind() != reflect.Ptr {
log.Errorf("method %v reply type not a pointer: %v", mname, replyType)
return nil
}
// Reply type must be exported.
if !isExportedOrBuiltinType(replyType) {
log.Errorf("method %v reply type not exported: %v", mname, replyType)
return nil
}
}
// Method needs one out.
if mtype.NumOut() != 1 {
log.Errorf("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 {
log.Errorf("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 server.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 server.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 lastStreamResponseError
}
}
// 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()
req := router.freeReq
if req == nil {
req = new(request)
} else {
router.freeReq = req.next
*req = request{}
}
router.reqLock.Unlock()
return req
}
func (router *router) freeRequest(req *request) {
router.reqLock.Lock()
req.next = router.freeReq
router.freeReq = req
router.reqLock.Unlock()
}
func (router *router) getResponse() *response {
router.respLock.Lock()
resp := router.freeResp
if resp == nil {
resp = new(response)
} else {
router.freeResp = resp.next
*resp = response{}
}
router.respLock.Unlock()
return resp
}
func (router *router) freeResponse(resp *response) {
router.respLock.Lock()
resp.next = router.freeResp
router.freeResp = resp
router.respLock.Unlock()
}
func (router *router) readRequest(r server.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 ...server.HandlerOption) server.Handler {
return newRpcHandler(h, opts...)
}
func (router *router) Handle(h server.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); 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 server.Request, rsp server.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 ...server.SubscriberOption) server.Subscriber {
return newSubscriber(topic, handler, opts...)
}
func (router *router) Subscribe(s server.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) String() string {
return "mucp"
}
func (router *router) ProcessMessage(ctx context.Context, msg server.Message) (err error) {
defer func() {
// recover any panics
if r := recover(); r != nil {
log.Errorf("panic recovered: %v", r)
log.Error(string(debug.Stack()))
err = merrors.InternalServerError("go.micro.server", "panic recovered: %v", r)
}
}()
router.su.RLock()
// get the subscribers by topic
subs, ok := router.subscribers[msg.Topic()]
// unlock since we only need to get the subs
router.su.RUnlock()
if !ok {
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
}
// read the body into the handler request value
if err = cc.ReadBody(req.Interface()); err != nil {
return err
}
// create the handler which will honour the SubscriberFunc type
fn := func(ctx context.Context, msg server.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
}

1013
server/mucp/rpc_server.go Normal file

File diff suppressed because it is too large Load Diff

105
server/mucp/rpc_stream.go Normal file
View File

@@ -0,0 +1,105 @@
package mucp
import (
"context"
"errors"
"io"
"sync"
"github.com/micro/go-micro/v3/codec"
"github.com/micro/go-micro/v3/server"
)
// Implements the Streamer interface
type rpcStream struct {
sync.RWMutex
id string
closed bool
err error
request server.Request
codec codec.Codec
context context.Context
}
func (r *rpcStream) Context() context.Context {
return r.context
}
func (r *rpcStream) Request() server.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 lastStreamResponseError.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()
}

View File

@@ -0,0 +1,133 @@
package mucp
import (
"bytes"
"fmt"
"io"
"math/rand"
"sync"
"testing"
"time"
"github.com/golang/protobuf/proto"
"github.com/micro/go-micro/v3/codec/json"
protoCodec "github.com/micro/go-micro/v3/codec/proto"
)
// protoStruct implements proto.Message
type protoStruct struct {
Payload string `protobuf:"bytes,1,opt,name=service,proto3" json:"service,omitempty"`
}
func (m *protoStruct) Reset() { *m = protoStruct{} }
func (m *protoStruct) String() string { return proto.CompactTextString(m) }
func (*protoStruct) ProtoMessage() {}
// safeBuffer throws away everything and wont Read data back
type safeBuffer struct {
sync.RWMutex
buf []byte
off int
}
func (b *safeBuffer) Write(p []byte) (n int, err error) {
if len(p) == 0 {
return 0, nil
}
// Cannot retain p, so we must copy it:
p2 := make([]byte, len(p))
copy(p2, p)
b.Lock()
b.buf = append(b.buf, p2...)
b.Unlock()
return len(p2), nil
}
func (b *safeBuffer) Read(p []byte) (n int, err error) {
if len(p) == 0 {
return 0, nil
}
b.RLock()
n = copy(p, b.buf[b.off:])
b.RUnlock()
if n == 0 {
return 0, io.EOF
}
b.off += n
return n, nil
}
func (b *safeBuffer) Close() error {
return nil
}
func TestRPCStream_Sequence(t *testing.T) {
buffer := new(bytes.Buffer)
rwc := readWriteCloser{
rbuf: buffer,
wbuf: buffer,
}
codec := json.NewCodec(&rwc)
streamServer := rpcStream{
codec: codec,
request: &rpcRequest{
codec: codec,
},
}
// Check if sequence is correct
for i := 0; i < 1000; i++ {
if err := streamServer.Send(fmt.Sprintf(`{"test":"value %d"}`, i)); err != nil {
t.Errorf("Unexpected Send error: %s", err)
}
}
for i := 0; i < 1000; i++ {
var msg string
if err := streamServer.Recv(&msg); err != nil {
t.Errorf("Unexpected Recv error: %s", err)
}
if msg != fmt.Sprintf(`{"test":"value %d"}`, i) {
t.Errorf("Unexpected msg: %s", msg)
}
}
}
func TestRPCStream_Concurrency(t *testing.T) {
buffer := new(safeBuffer)
codec := protoCodec.NewCodec(buffer)
streamServer := rpcStream{
codec: codec,
request: &rpcRequest{
codec: codec,
},
}
var wg sync.WaitGroup
// Check if race conditions happen
for i := 0; i < 10; i++ {
wg.Add(2)
go func() {
for i := 0; i < 50; i++ {
msg := protoStruct{Payload: "test"}
<-time.After(time.Duration(rand.Intn(50)) * time.Millisecond)
if err := streamServer.Send(msg); err != nil {
t.Errorf("Unexpected Send error: %s", err)
}
}
wg.Done()
}()
go func() {
for i := 0; i < 50; i++ {
<-time.After(time.Duration(rand.Intn(50)) * time.Millisecond)
if err := streamServer.Recv(&protoStruct{}); err != nil {
t.Errorf("Unexpected Recv error: %s", err)
}
}
wg.Done()
}()
}
wg.Wait()
}

32
server/mucp/rpc_util.go Normal file
View File

@@ -0,0 +1,32 @@
package mucp
import (
"sync"
)
// waitgroup for global management of connections
type waitGroup struct {
// local waitgroup
lg sync.WaitGroup
// global waitgroup
gg *sync.WaitGroup
}
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()
}

176
server/mucp/subscriber.go Normal file
View File

@@ -0,0 +1,176 @@
package mucp
import (
"fmt"
"reflect"
"github.com/micro/go-micro/v3/registry"
"github.com/micro/go-micro/v3/server"
)
const (
subSig = "func(context.Context, interface{}) error"
)
type handler struct {
method reflect.Value
reqType reflect.Type
ctxType reflect.Type
}
type subscriber struct {
topic string
rcvr reflect.Value
typ reflect.Type
subscriber interface{}
handlers []*handler
endpoints []*registry.Endpoint
opts server.SubscriberOptions
}
func newSubscriber(topic string, sub interface{}, opts ...server.SubscriberOption) server.Subscriber {
options := server.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 server.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() server.SubscriberOptions {
return s.opts
}