chore: import upstream snapshot with attribution
govulncheck / govulncheck (push) Has been cancelled
Lint / golangci-lint (push) Has been cancelled
Run Tests / Unit Tests (push) Has been cancelled
Run Tests / Etcd Integration Tests (push) Has been cancelled
Harness (E2E) / Harnesses (mock LLM) (push) Has been cancelled
Harness (E2E) / Provider harnesses (live LLM conformance) (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 12:40:33 +08:00
commit e071084ebe
982 changed files with 160368 additions and 0 deletions
+164
View File
@@ -0,0 +1,164 @@
package server
import (
"go/ast"
"go/doc"
"go/parser"
"go/token"
"reflect"
"regexp"
"runtime"
"strings"
)
var (
examplePattern = regexp.MustCompile(`@example\s+([\s\S]+?)(?:\n\s*\n|$)`)
)
// extractMethodDoc extracts documentation from a method's Go doc comment
func extractMethodDoc(method reflect.Method, rcvrType reflect.Type) (description, example string) {
// Get the function's source location
fn := method.Func
if !fn.IsValid() {
return "", ""
}
pc := fn.Pointer()
if pc == 0 {
return "", ""
}
// Get the source file location
funcForPC := runtime.FuncForPC(pc)
if funcForPC == nil {
return "", ""
}
file, _ := funcForPC.FileLine(pc)
if file == "" {
return "", ""
}
// Parse the source file
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, file, nil, parser.ParseComments)
if err != nil {
return "", ""
}
// Find the receiver type name (e.g., "Users" from *Users)
rcvrTypeName := rcvrType.Name()
if rcvrTypeName == "" && rcvrType.Kind() == reflect.Pointer {
rcvrTypeName = rcvrType.Elem().Name()
}
// Search for the method in the AST
for _, decl := range f.Decls {
funcDecl, ok := decl.(*ast.FuncDecl)
if !ok {
continue
}
// Check if this is a method (has receiver)
if funcDecl.Recv == nil {
continue
}
// Check if method name matches
if funcDecl.Name.Name != method.Name {
continue
}
// Check if receiver type matches
if len(funcDecl.Recv.List) > 0 {
recvTypeName := getTypeName(funcDecl.Recv.List[0].Type)
if recvTypeName != rcvrTypeName {
continue
}
}
// Found the method! Extract its doc comment
if funcDecl.Doc != nil {
comment := funcDecl.Doc.Text()
return parseComment(comment)
}
}
return "", ""
}
// getTypeName extracts the type name from an AST expression
func getTypeName(expr ast.Expr) string {
switch t := expr.(type) {
case *ast.Ident:
return t.Name
case *ast.StarExpr:
return getTypeName(t.X)
default:
return ""
}
}
// parseComment extracts description and example from a doc comment
func parseComment(comment string) (description, example string) {
// Extract @example if present
if match := examplePattern.FindStringSubmatch(comment); len(match) > 1 {
example = strings.TrimSpace(match[1])
// Remove @example section from description
comment = examplePattern.ReplaceAllString(comment, "")
}
// Clean up the description
description = strings.TrimSpace(comment)
// Use doc.Synopsis for the first sentence if description is long
if len(description) > 200 {
synopsis := doc.Synopsis(description)
if synopsis != "" {
description = synopsis
}
}
return description, example
}
// extractHandlerDocs extracts documentation for all methods of a handler
func extractHandlerDocs(handler interface{}) map[string]map[string]string {
metadata := make(map[string]map[string]string)
typ := reflect.TypeOf(handler)
if typ == nil {
return metadata
}
// Get the receiver type for methods
rcvrType := typ
if rcvrType.Kind() == reflect.Pointer {
rcvrType = rcvrType.Elem()
}
// Iterate through methods
for i := 0; i < typ.NumMethod(); i++ {
method := typ.Method(i)
// Skip non-exported methods
if method.PkgPath != "" {
continue
}
// Extract documentation from source
description, example := extractMethodDoc(method, rcvrType)
if description != "" || example != "" {
metadata[method.Name] = make(map[string]string)
if description != "" {
metadata[method.Name]["description"] = description
}
if example != "" {
metadata[method.Name]["example"] = example
}
}
}
return metadata
}
+158
View File
@@ -0,0 +1,158 @@
package server
import (
"context"
"testing"
)
// TestService is a test service with documented methods
type TestService struct{}
// GetItem retrieves an item by ID. Returns the item if found, error otherwise.
//
// @example {"id": "item-123"}
func (s *TestService) GetItem(ctx context.Context, req *TestRequest, rsp *TestResponse) error {
return nil
}
// CreateItem creates a new item in the system.
//
// @example {"name": "New Item", "value": 42}
func (s *TestService) CreateItem(ctx context.Context, req *TestRequest, rsp *TestResponse) error {
return nil
}
func (s *TestService) NoDoc(ctx context.Context, req *TestRequest, rsp *TestResponse) error {
return nil
}
type TestRequest struct{}
type TestResponse struct{}
func TestExtractHandlerDocs(t *testing.T) {
handler := &TestService{}
docs := extractHandlerDocs(handler)
// Test GetItem extraction
if docs["GetItem"] == nil {
t.Fatal("GetItem documentation not extracted")
}
if docs["GetItem"]["description"] == "" {
t.Error("GetItem description is empty")
}
if docs["GetItem"]["example"] != `{"id": "item-123"}` {
t.Errorf("GetItem example = %q, want %q", docs["GetItem"]["example"], `{"id": "item-123"}`)
}
// Test CreateItem extraction
if docs["CreateItem"] == nil {
t.Fatal("CreateItem documentation not extracted")
}
if docs["CreateItem"]["description"] == "" {
t.Error("CreateItem description is empty")
}
if docs["CreateItem"]["example"] != `{"name": "New Item", "value": 42}` {
t.Errorf("CreateItem example = %q, want %q", docs["CreateItem"]["example"], `{"name": "New Item", "value": 42}`)
}
// Test NoDoc (should have no metadata or only empty metadata)
if len(docs["NoDoc"]) > 0 {
t.Logf("NoDoc metadata: %+v", docs["NoDoc"])
// Check if all values are empty
allEmpty := true
for _, v := range docs["NoDoc"] {
if v != "" {
allEmpty = false
break
}
}
if !allEmpty {
t.Error("NoDoc should have no metadata with values")
}
}
}
func TestNewRpcHandlerAutoExtract(t *testing.T) {
handler := NewRpcHandler(&TestService{})
rpcHandler := handler.(*RpcHandler)
// Check that endpoints have metadata
var foundGetItem bool
for _, ep := range rpcHandler.Endpoints() {
if ep.Name == "TestService.GetItem" {
foundGetItem = true
if ep.Metadata["description"] == "" {
t.Error("GetItem endpoint missing description metadata")
}
if ep.Metadata["example"] != `{"id": "item-123"}` {
t.Errorf("GetItem endpoint example = %q, want %q", ep.Metadata["example"], `{"id": "item-123"}`)
}
}
}
if !foundGetItem {
t.Error("GetItem endpoint not found")
}
}
func TestManualMetadataOverridesAutoExtract(t *testing.T) {
// Manual metadata should take precedence over auto-extracted
handler := NewRpcHandler(
&TestService{},
WithEndpointDocs(map[string]EndpointDoc{
"TestService.GetItem": {
Description: "Manual override description",
Example: `{"id": "manual-123"}`,
},
}),
)
rpcHandler := handler.(*RpcHandler)
for _, ep := range rpcHandler.Endpoints() {
if ep.Name == "TestService.GetItem" {
if ep.Metadata["description"] != "Manual override description" {
t.Errorf("Manual description not used: got %q", ep.Metadata["description"])
}
if ep.Metadata["example"] != `{"id": "manual-123"}` {
t.Errorf("Manual example not used: got %q", ep.Metadata["example"])
}
return
}
}
t.Error("GetItem endpoint not found")
}
func TestWithEndpointScopes(t *testing.T) {
handler := NewRpcHandler(
&TestService{},
WithEndpointScopes("TestService.GetItem", "items:read"),
WithEndpointScopes("TestService.CreateItem", "items:write", "items:admin"),
)
rpcHandler := handler.(*RpcHandler)
var foundGet, foundCreate bool
for _, ep := range rpcHandler.Endpoints() {
switch ep.Name {
case "TestService.GetItem":
foundGet = true
if ep.Metadata["scopes"] != "items:read" {
t.Errorf("GetItem scopes = %q, want %q", ep.Metadata["scopes"], "items:read")
}
case "TestService.CreateItem":
foundCreate = true
if ep.Metadata["scopes"] != "items:write,items:admin" {
t.Errorf("CreateItem scopes = %q, want %q", ep.Metadata["scopes"], "items:write,items:admin")
}
}
}
if !foundGet {
t.Error("GetItem endpoint not found")
}
if !foundCreate {
t.Error("CreateItem endpoint not found")
}
}
+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)
}
+98
View File
@@ -0,0 +1,98 @@
package server
import "strings"
// Package server provides options for documenting service endpoints.
//
// Documentation is AUTOMATICALLY EXTRACTED from Go doc comments on handler methods.
// You don't need any extra code - just write good comments!
//
// Basic usage (automatic):
//
// // GetUser retrieves a user by ID from the database.
// //
// // @example {"id": "user-123"}
// func (s *UserService) GetUser(ctx context.Context, req *GetUserRequest, rsp *GetUserResponse) error {
// // implementation
// }
//
// // Register handler - docs extracted automatically from comments
// server.Handle(server.NewHandler(new(UserService)))
//
// Advanced usage (manual override):
//
// // Override auto-extracted docs with manual metadata
// server.Handle(
// server.NewHandler(
// new(UserService),
// server.WithEndpointDocs(map[string]server.EndpointDoc{
// "UserService.GetUser": {
// Description: "Custom description overrides comment",
// Example: `{"id": "user-123"}`,
// },
// }),
// ),
// )
// EndpointDoc contains documentation for an endpoint
type EndpointDoc struct {
Description string // What the endpoint does
Example string // Example JSON input
}
// WithEndpointDocs returns a HandlerOption that adds documentation to multiple endpoints.
// This metadata is stored in the registry and used by MCP gateway to generate
// rich tool descriptions for AI agents.
//
// This is a convenience wrapper around EndpointMetadata for adding docs to multiple endpoints at once.
func WithEndpointDocs(docs map[string]EndpointDoc) HandlerOption {
return func(o *HandlerOptions) {
if o.Metadata == nil {
o.Metadata = make(map[string]map[string]string)
}
for endpoint, doc := range docs {
if o.Metadata[endpoint] == nil {
o.Metadata[endpoint] = make(map[string]string)
}
if doc.Description != "" {
o.Metadata[endpoint]["description"] = doc.Description
}
if doc.Example != "" {
o.Metadata[endpoint]["example"] = doc.Example
}
}
}
}
// WithEndpointDescription is a convenience function for adding a description to a single endpoint.
// For multiple endpoints, use WithEndpointDocs instead.
func WithEndpointDescription(endpoint, description string) HandlerOption {
return EndpointMetadata(endpoint, map[string]string{
"description": description,
})
}
// WithEndpointExample is a convenience function for adding an example to a single endpoint.
func WithEndpointExample(endpoint, example string) HandlerOption {
return EndpointMetadata(endpoint, map[string]string{
"example": example,
})
}
// WithEndpointScopes sets the required auth scopes for a single endpoint.
// Scopes are stored as comma-separated values in endpoint metadata and
// enforced by the MCP gateway when an Auth provider is configured.
//
// Example:
//
// server.NewHandler(
// new(BlogService),
// server.WithEndpointScopes("Blog.Create", "blog:write", "blog:admin"),
// server.WithEndpointScopes("Blog.Read", "blog:read"),
// )
func WithEndpointScopes(endpoint string, scopes ...string) HandlerOption {
return EndpointMetadata(endpoint, map[string]string{
"scopes": strings.Join(scopes, ","),
})
}
+133
View File
@@ -0,0 +1,133 @@
package server
import (
"fmt"
"reflect"
"strings"
"go-micro.dev/v6/registry"
)
func extractValue(v reflect.Type, d int) *registry.Value {
if d == 3 {
return nil
}
if v == nil {
return nil
}
if v.Kind() == reflect.Pointer {
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]
} else {
val.Name = f.Name
}
// 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.Pointer {
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)
}
+64
View File
@@ -0,0 +1,64 @@
package server
import (
"context"
"reflect"
"testing"
"go-micro.dev/v6/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)
}
}
+182
View File
@@ -0,0 +1,182 @@
package grpc
import (
"encoding/json"
"strings"
"go-micro.dev/v6/codec"
"go-micro.dev/v6/codec/bytes"
"google.golang.org/grpc"
"google.golang.org/grpc/encoding"
"google.golang.org/grpc/metadata"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
)
type jsonCodec struct{}
type bytesCodec struct{}
type protoCodec struct{}
type wrapCodec struct{ encoding.Codec }
var protojsonMarshaler = protojson.MarshalOptions{
UseProtoNames: true,
EmitUnpopulated: false,
}
var (
defaultGRPCCodecs = map[string]encoding.Codec{
"application/json": jsonCodec{},
"application/proto": protoCodec{},
"application/protobuf": protoCodec{},
"application/octet-stream": protoCodec{},
"application/grpc": protoCodec{},
"application/grpc+json": jsonCodec{},
"application/grpc+proto": protoCodec{},
"application/grpc+bytes": bytesCodec{},
}
)
func (w wrapCodec) String() string {
return w.Name()
}
func (w wrapCodec) Marshal(v interface{}) ([]byte, error) {
b, ok := v.(*bytes.Frame)
if ok {
return b.Data, nil
}
return w.Codec.Marshal(v)
}
func (w wrapCodec) Unmarshal(data []byte, v interface{}) error {
b, ok := v.(*bytes.Frame)
if ok {
b.Data = data
return nil
}
if v == nil {
return nil
}
return w.Codec.Unmarshal(data, v)
}
func (protoCodec) Marshal(v interface{}) ([]byte, error) {
m, ok := v.(proto.Message)
if !ok {
return nil, codec.ErrInvalidMessage
}
return proto.Marshal(m)
}
func (protoCodec) Unmarshal(data []byte, v interface{}) error {
m, ok := v.(proto.Message)
if !ok {
return codec.ErrInvalidMessage
}
return proto.Unmarshal(data, m)
}
func (protoCodec) Name() string {
return "proto"
}
func (jsonCodec) Marshal(v interface{}) ([]byte, error) {
if pb, ok := v.(proto.Message); ok {
return protojsonMarshaler.Marshal(pb)
}
return json.Marshal(v)
}
func (jsonCodec) Unmarshal(data []byte, v interface{}) error {
if len(data) == 0 {
return nil
}
if pb, ok := v.(proto.Message); ok {
return protojson.Unmarshal(data, pb)
}
return json.Unmarshal(data, v)
}
func (jsonCodec) Name() string {
return "json"
}
func (bytesCodec) Marshal(v interface{}) ([]byte, error) {
b, ok := v.(*[]byte)
if !ok {
return nil, codec.ErrInvalidMessage
}
return *b, nil
}
func (bytesCodec) Unmarshal(data []byte, v interface{}) error {
b, ok := v.(*[]byte)
if !ok {
return codec.ErrInvalidMessage
}
*b = data
return nil
}
func (bytesCodec) Name() string {
return "bytes"
}
type grpcCodec struct {
// headers
id string
target string
method string
endpoint string
s grpc.ServerStream
c encoding.Codec
}
func (g *grpcCodec) ReadHeader(m *codec.Message, mt codec.MessageType) error {
md, _ := metadata.FromIncomingContext(g.s.Context())
if m == nil {
m = new(codec.Message)
}
if m.Header == nil {
m.Header = make(map[string]string, len(md))
}
for k, v := range md {
m.Header[k] = strings.Join(v, ",")
}
m.Id = g.id
m.Target = g.target
m.Method = g.method
m.Endpoint = g.endpoint
return nil
}
func (g *grpcCodec) ReadBody(v interface{}) error {
// caller has requested a frame
if f, ok := v.(*bytes.Frame); ok {
return g.s.RecvMsg(f)
}
return g.s.RecvMsg(v)
}
func (g *grpcCodec) Write(m *codec.Message, v interface{}) error {
// if we don't have a body
if v != nil {
b, err := g.c.Marshal(v)
if err != nil {
return err
}
m.Body = b
}
// write the body using the framing codec
return g.s.SendMsg(&bytes.Frame{Data: m.Body})
}
func (g *grpcCodec) Close() error {
return nil
}
func (g *grpcCodec) String() string {
return "grpc"
}
+16
View File
@@ -0,0 +1,16 @@
package grpc
import (
"context"
"go-micro.dev/v6/server"
)
func setServerOption(k, v interface{}) server.Option {
return func(o *server.Options) {
if o.Context == nil {
o.Context = context.Background()
}
o.Context = context.WithValue(o.Context, k, v)
}
}
+42
View File
@@ -0,0 +1,42 @@
package grpc
import (
"net/http"
"go-micro.dev/v6/errors"
"google.golang.org/grpc/codes"
)
func microError(err *errors.Error) codes.Code {
switch err {
case nil:
return codes.OK
}
switch err.Code {
case http.StatusOK:
return codes.OK
case http.StatusBadRequest:
return codes.InvalidArgument
case http.StatusRequestTimeout:
return codes.DeadlineExceeded
case http.StatusNotFound:
return codes.NotFound
case http.StatusConflict:
return codes.AlreadyExists
case http.StatusForbidden:
return codes.PermissionDenied
case http.StatusUnauthorized:
return codes.Unauthenticated
case http.StatusPreconditionFailed:
return codes.FailedPrecondition
case http.StatusNotImplemented:
return codes.Unimplemented
case http.StatusInternalServerError:
return codes.Internal
case http.StatusServiceUnavailable:
return codes.Unavailable
}
return codes.Unknown
}
+125
View File
@@ -0,0 +1,125 @@
package grpc
import (
"fmt"
"reflect"
"strings"
"go-micro.dev/v6/registry"
)
func extractValue(v reflect.Type, d int) *registry.Value {
if d == 3 {
return nil
}
if v == nil {
return nil
}
if v.Kind() == reflect.Pointer {
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
}
arg.Values = append(arg.Values, val)
}
case reflect.Slice:
p := v.Elem()
if p.Kind() == reflect.Pointer {
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),
}
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)
}
+64
View File
@@ -0,0 +1,64 @@
package grpc
import (
"context"
"reflect"
"testing"
"go-micro.dev/v6/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)
}
}
+146
View File
@@ -0,0 +1,146 @@
package grpc
import (
"context"
"net"
"testing"
"time"
micro "go-micro.dev/v6"
"go-micro.dev/v6/client"
grpcclient "go-micro.dev/v6/client/grpc"
"go-micro.dev/v6/registry"
"go-micro.dev/v6/server"
)
type SleepRequest struct {
DelayMS int `json:"delay_ms"`
}
type SleepResponse struct {
Message string `json:"message"`
}
type SleepHandler struct {
started chan struct{}
}
func (h *SleepHandler) Sleep(ctx context.Context, req *SleepRequest, rsp *SleepResponse) error {
select {
case h.started <- struct{}{}:
default:
}
timer := time.NewTimer(time.Duration(req.DelayMS) * time.Millisecond)
defer timer.Stop()
select {
case <-ctx.Done():
return ctx.Err()
case <-timer.C:
}
rsp.Message = "ok"
return nil
}
func TestGracefulStopRejectsNewRPCsButAllowsInFlightRPCs(t *testing.T) {
listener, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("listen: %v", err)
}
defer listener.Close()
addr := listener.Addr().String()
reg := registry.NewMemoryRegistry()
handler := &SleepHandler{started: make(chan struct{}, 1)}
svc := micro.NewService("grace-demo",
micro.HandleSignal(false),
micro.Registry(reg),
micro.Server(NewServer(
server.Registry(reg),
server.Name("grace-demo"),
server.Address(addr),
Listener(listener),
GracefulStopTimeout(3*time.Second),
)),
micro.Client(grpcclient.NewClient(
client.Registry(reg),
client.ContentType("application/grpc+json"),
client.DialTimeout(200*time.Millisecond),
client.RequestTimeout(5*time.Second),
)),
)
if err := svc.Handle(handler); err != nil {
t.Fatalf("handle: %v", err)
}
if err := svc.Start(); err != nil {
t.Fatalf("start: %v", err)
}
stopped := false
defer func() {
if !stopped {
_ = svc.Stop()
}
}()
longDone := make(chan error, 1)
go func() {
req := svc.Client().NewRequest("grace-demo", "SleepHandler.Sleep", &SleepRequest{DelayMS: 1000})
rsp := &SleepResponse{}
longDone <- svc.Client().Call(context.Background(), req, rsp, client.WithAddress(addr))
}()
select {
case <-handler.started:
case <-time.After(2 * time.Second):
t.Fatal("timed out waiting for long RPC to start")
}
stopDone := make(chan error, 1)
go func() {
stopDone <- svc.Stop()
}()
freshReq := svc.Client().NewRequest("grace-demo", "SleepHandler.Sleep", &SleepRequest{DelayMS: 10})
freshRsp := &SleepResponse{}
var rejectErr error
deadline := time.Now().Add(300 * time.Millisecond)
for time.Now().Before(deadline) {
callCtx, cancel := context.WithTimeout(context.Background(), 150*time.Millisecond)
err := svc.Client().Call(callCtx, freshReq, freshRsp, client.WithAddress(addr))
cancel()
if err != nil {
rejectErr = err
break
}
time.Sleep(10 * time.Millisecond)
}
if rejectErr == nil {
t.Fatal("expected a new RPC to be rejected shortly after shutdown started")
}
select {
case err := <-longDone:
if err != nil {
t.Fatalf("long RPC failed during graceful stop: %v", err)
}
case <-time.After(2 * time.Second):
t.Fatal("timed out waiting for in-flight RPC to finish")
}
select {
case err := <-stopDone:
if err != nil {
t.Fatalf("stop: %v", err)
}
stopped = true
case <-time.After(2 * time.Second):
t.Fatal("timed out waiting for server stop")
}
}
+1051
View File
File diff suppressed because it is too large Load Diff
+66
View File
@@ -0,0 +1,66 @@
package grpc
import (
"reflect"
"go-micro.dev/v6/registry"
"go-micro.dev/v6/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
}
+102
View File
@@ -0,0 +1,102 @@
package grpc
import (
"context"
"crypto/tls"
"net"
"time"
"go-micro.dev/v6/broker"
"go-micro.dev/v6/codec"
"go-micro.dev/v6/logger"
"go-micro.dev/v6/registry"
"go-micro.dev/v6/server"
"go-micro.dev/v6/transport"
"google.golang.org/grpc"
"google.golang.org/grpc/encoding"
)
type codecsKey struct{}
type grpcOptions struct{}
type netListener struct{}
type maxMsgSizeKey struct{}
type maxConnKey struct{}
type tlsAuth struct{}
type grpcServerKey struct{}
type gracefulStopTimeoutKey struct{}
// gRPC Codec to be used to encode/decode requests for a given content type.
func Codec(contentType string, c encoding.Codec) server.Option {
return func(o *server.Options) {
codecs := make(map[string]encoding.Codec)
if o.Context == nil {
o.Context = context.Background()
}
if v, ok := o.Context.Value(codecsKey{}).(map[string]encoding.Codec); ok && v != nil {
codecs = v
}
codecs[contentType] = c
o.Context = context.WithValue(o.Context, codecsKey{}, codecs)
}
}
// AuthTLS should be used to setup a secure authentication using TLS.
func AuthTLS(t *tls.Config) server.Option {
return setServerOption(tlsAuth{}, t)
}
// MaxConn specifies maximum number of max simultaneous connections to server.
func MaxConn(n int) server.Option {
return setServerOption(maxConnKey{}, n)
}
// Listener specifies the net.Listener to use instead of the default.
func Listener(l net.Listener) server.Option {
return setServerOption(netListener{}, l)
}
// Server specifies a *grpc.Server to use instead of the default
// This is for rare use case where user need to expose grpc.Server for
// customization. Please NOTE however user injected grpcServer doesn't support
// server Handler abstraction.
func Server(srv *grpc.Server) server.Option {
return setServerOption(grpcServerKey{}, srv)
}
// Options to be used to configure gRPC options.
func Options(opts ...grpc.ServerOption) server.Option {
return setServerOption(grpcOptions{}, opts)
}
// MaxMsgSize set the maximum message in bytes the server can receive and
// send. Default maximum message size is 4 MB.
func MaxMsgSize(s int) server.Option {
return setServerOption(maxMsgSizeKey{}, s)
}
// GracefulStopTimeout sets how long Stop waits for active RPCs before forcing Stop.
func GracefulStopTimeout(timeout time.Duration) server.Option {
return setServerOption(gracefulStopTimeoutKey{}, timeout)
}
func newOptions(opt ...server.Option) server.Options {
opts := server.Options{
Codecs: make(map[string]codec.NewCodec),
Metadata: map[string]string{},
Broker: broker.DefaultBroker,
Registry: registry.DefaultRegistry,
RegisterCheck: server.DefaultRegisterCheck,
Transport: transport.DefaultTransport,
Address: server.DefaultAddress,
Name: server.DefaultName,
Id: server.DefaultId,
Version: server.DefaultVersion,
Logger: logger.DefaultLogger,
}
for _, o := range opt {
o(&opts)
}
return opts
}
+90
View File
@@ -0,0 +1,90 @@
package grpc
import (
"go-micro.dev/v6/codec"
"go-micro.dev/v6/codec/bytes"
)
type rpcRequest struct {
service string
method string
contentType string
codec codec.Codec
header map[string]string
body []byte
stream bool
payload interface{}
}
type rpcMessage struct {
topic string
contentType string
payload interface{}
header map[string]string
body []byte
codec codec.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.method
}
func (r *rpcRequest) Codec() codec.Reader {
return r.codec
}
func (r *rpcRequest) Header() map[string]string {
return r.header
}
func (r *rpcRequest) Read() ([]byte, error) {
f := &bytes.Frame{}
if err := r.codec.ReadBody(f); err != nil {
return nil, err
}
return f.Data, nil
}
func (r *rpcRequest) Stream() bool {
return r.stream
}
func (r *rpcRequest) Body() interface{} {
return r.payload
}
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 {
return r.codec
}
+27
View File
@@ -0,0 +1,27 @@
package grpc
import (
"go-micro.dev/v6/codec"
)
type rpcResponse struct {
header map[string]string
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 {
return r.codec.Write(&codec.Message{
Header: r.header,
Body: b,
}, nil)
}
+182
View File
@@ -0,0 +1,182 @@
package grpc
// 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"
"reflect"
"sync"
"unicode"
"unicode/utf8"
"go-micro.dev/v6/logger"
"go-micro.dev/v6/server"
)
var (
// 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 {
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
}
// server represents an RPC Server.
type rServer struct {
mu sync.Mutex // protects the serviceMap
serviceMap map[string]*service
logger logger.Logger
}
// 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.Pointer {
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() == ""
}
// prepareEndpoint() returns a methodType for the provided method or nil
// in case if the method was unsuitable.
func prepareEndpoint(method reflect.Method, log logger.Logger) *methodType {
mtype := method.Type
mname := method.Name
var replyType, argType, contextType reflect.Type
var stream bool
// Endpoint() 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.Logf(logger.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((*server.Stream)(nil)).Elem()
if !argType.Implements(streamType) {
log.Logf(logger.ErrorLevel, "%v argument does not implement Streamer interface: %v", mname, argType)
return nil
}
} else {
// if not stream check the replyType
// First arg need not be a pointer.
if !isExportedOrBuiltinType(argType) {
log.Logf(logger.ErrorLevel, "%v argument type not exported: %v", mname, argType)
return nil
}
if replyType.Kind() != reflect.Pointer {
log.Logf(logger.ErrorLevel, "method %v reply type not a pointer: %v", mname, replyType)
return nil
}
// Reply type must be exported.
if !isExportedOrBuiltinType(replyType) {
log.Logf(logger.ErrorLevel, "method %v reply type not exported: %v", mname, replyType)
return nil
}
}
// Endpoint() needs one out.
if mtype.NumOut() != 1 {
log.Logf(logger.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 {
log.Logf(logger.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 (server *rServer) register(rcvr interface{}) error {
server.mu.Lock()
defer server.mu.Unlock()
log := server.logger
if server.serviceMap == nil {
server.serviceMap = make(map[string]*service)
}
s := new(service)
s.typ = reflect.TypeOf(rcvr)
s.rcvr = reflect.ValueOf(rcvr)
sname := reflect.Indirect(s.rcvr).Type().Name()
if sname == "" {
logger.Fatalf("rpc: no service name for type %v", s.typ.String())
}
if !isExported(sname) {
s := "rpc Register: type " + sname + " is not exported"
log.Log(logger.ErrorLevel, s)
return errors.New(s)
}
if _, present := server.serviceMap[sname]; present {
return errors.New("rpc: service already defined: " + sname)
}
s.name = sname
s.method = make(map[string]*methodType)
// Install the methods
for m := 0; m < s.typ.NumMethod(); m++ {
method := s.typ.Method(m)
if mt := prepareEndpoint(method, log); mt != nil {
s.method[method.Name] = mt
}
}
if len(s.method) == 0 {
s := "rpc Register: type " + sname + " has no exported methods of suitable type"
log.Log(logger.ErrorLevel, s)
return errors.New(s)
}
server.serviceMap[s.name] = s
return nil
}
func (m *methodType) prepareContext(ctx context.Context) reflect.Value {
if contextv := reflect.ValueOf(ctx); contextv.IsValid() {
return contextv
}
return reflect.Zero(m.ContextType)
}
+38
View File
@@ -0,0 +1,38 @@
package grpc
import (
"context"
"go-micro.dev/v6/server"
"google.golang.org/grpc"
)
// rpcStream implements a server side Stream.
type rpcStream struct {
s grpc.ServerStream
request server.Request
}
func (r *rpcStream) Close() error {
return nil
}
func (r *rpcStream) Error() error {
return nil
}
func (r *rpcStream) Request() server.Request {
return r.request
}
func (r *rpcStream) Context() context.Context {
return r.s.Context()
}
func (r *rpcStream) Send(m interface{}) error {
return r.s.SendMsg(m)
}
func (r *rpcStream) Recv(m interface{}) error {
return r.s.RecvMsg(m)
}
+291
View File
@@ -0,0 +1,291 @@
package grpc
import (
"context"
"fmt"
"reflect"
"runtime/debug"
"strings"
"go-micro.dev/v6/broker"
"go-micro.dev/v6/errors"
"go-micro.dev/v6/logger"
"go-micro.dev/v6/metadata"
"go-micro.dev/v6/registry"
"go-micro.dev/v6/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 (g *grpcServer) createSubHandler(sb *subscriber, opts server.Options) broker.Handler {
return func(p broker.Event) (err error) {
defer func() {
if r := recover(); r != nil {
g.opts.Logger.Log(logger.ErrorLevel, "panic recovered: ", r)
g.opts.Logger.Log(logger.ErrorLevel, string(debug.Stack()))
err = errors.InternalServerError("go.micro.server", "panic recovered: %v", r)
}
}()
msg := p.Message()
// if we don't have headers, create empty map
if msg.Header == nil {
msg.Header = make(map[string]string)
}
ct := msg.Header["Content-Type"]
if len(ct) == 0 {
msg.Header["Content-Type"] = defaultContentType
ct = defaultContentType
}
cf, err := g.newGRPCCodec(ct)
if err != nil {
return err
}
hdr := make(map[string]string, len(msg.Header))
for k, v := range msg.Header {
hdr[k] = v
}
delete(hdr, "Content-Type")
ctx := metadata.NewContext(context.Background(), hdr)
results := make(chan error, len(sb.handlers))
for i := 0; i < len(sb.handlers); i++ {
handler := sb.handlers[i]
var isVal bool
var req reflect.Value
if handler.reqType.Kind() == reflect.Pointer {
req = reflect.New(handler.reqType.Elem())
} else {
req = reflect.New(handler.reqType)
isVal = true
}
if isVal {
req = req.Elem()
}
if err = cf.Unmarshal(msg.Body, req.Interface()); err != nil {
return err
}
fn := func(ctx context.Context, msg server.Message) error {
var vals []reflect.Value
if sb.typ.Kind() != reflect.Func {
vals = append(vals, sb.rcvr)
}
if handler.ctxType != nil {
vals = append(vals, reflect.ValueOf(ctx))
}
vals = append(vals, reflect.ValueOf(msg.Payload()))
returnValues := handler.method.Call(vals)
if rerr := returnValues[0].Interface(); rerr != nil {
return rerr.(error)
}
return nil
}
for i := len(opts.SubWrappers); i > 0; i-- {
fn = opts.SubWrappers[i-1](fn)
}
if g.wg != nil {
g.wg.Add(1)
}
go func() {
if g.wg != nil {
defer g.wg.Done()
}
err := fn(ctx, &rpcMessage{
topic: sb.topic,
contentType: ct,
payload: req.Interface(),
header: msg.Header,
body: msg.Body,
})
results <- err
}()
}
var errors []string
for i := 0; i < len(sb.handlers); i++ {
if rerr := <-results; rerr != nil {
errors = append(errors, rerr.Error())
}
}
if len(errors) > 0 {
err = fmt.Errorf("subscriber error: %s", strings.Join(errors, "\n"))
}
return err
}
}
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
}
+49
View File
@@ -0,0 +1,49 @@
package grpc
import (
"context"
"io"
"os"
"sync"
"google.golang.org/grpc/codes"
)
// convertCode converts a standard Go error into its canonical code. Note that
// this is only used to translate the error returned by the server applications.
func convertCode(err error) codes.Code {
switch err {
case nil:
return codes.OK
case io.EOF:
return codes.OutOfRange
case io.ErrClosedPipe, io.ErrNoProgress, io.ErrShortBuffer, io.ErrShortWrite, io.ErrUnexpectedEOF:
return codes.FailedPrecondition
case os.ErrInvalid:
return codes.InvalidArgument
case context.Canceled:
return codes.Canceled
case context.DeadlineExceeded:
return codes.DeadlineExceeded
}
switch {
case os.IsExist(err):
return codes.AlreadyExists
case os.IsNotExist(err):
return codes.NotFound
case os.IsPermission(err):
return codes.PermissionDenied
}
return codes.Unknown
}
func wait(ctx context.Context) *sync.WaitGroup {
if ctx == nil {
return nil
}
wg, ok := ctx.Value("wait").(*sync.WaitGroup)
if !ok {
return nil
}
return wg
}
+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
}
}
+139
View File
@@ -0,0 +1,139 @@
package mock
import (
"errors"
"sync"
"github.com/google/uuid"
"go-micro.dev/v6/server"
)
type MockServer struct {
Opts server.Options
Handlers map[string]server.Handler
Subscribers map[string][]server.Subscriber
sync.Mutex
Running bool
}
var (
_ server.Server = NewServer()
)
func newMockServer(opts ...server.Option) *MockServer {
var options server.Options
for _, o := range opts {
o(&options)
}
return &MockServer{
Opts: options,
Handlers: make(map[string]server.Handler),
Subscribers: make(map[string][]server.Subscriber),
}
}
func (m *MockServer) Options() server.Options {
m.Lock()
defer m.Unlock()
return m.Opts
}
func (m *MockServer) Init(opts ...server.Option) error {
m.Lock()
defer m.Unlock()
for _, o := range opts {
o(&m.Opts)
}
return nil
}
func (m *MockServer) Handle(h server.Handler) error {
m.Lock()
defer m.Unlock()
if _, ok := m.Handlers[h.Name()]; ok {
return errors.New("Handler " + h.Name() + " already exists")
}
m.Handlers[h.Name()] = h
return nil
}
func (m *MockServer) NewHandler(h interface{}, opts ...server.HandlerOption) server.Handler {
var options server.HandlerOptions
for _, o := range opts {
o(&options)
}
return &MockHandler{
Id: uuid.New().String(),
Hdlr: h,
Opts: options,
}
}
func (m *MockServer) NewSubscriber(topic string, fn interface{}, opts ...server.SubscriberOption) server.Subscriber {
var options server.SubscriberOptions
for _, o := range opts {
o(&options)
}
return &MockSubscriber{
Id: topic,
Sub: fn,
Opts: options,
}
}
func (m *MockServer) Subscribe(sub server.Subscriber) error {
m.Lock()
defer m.Unlock()
subs := m.Subscribers[sub.Topic()]
subs = append(subs, sub)
m.Subscribers[sub.Topic()] = subs
return nil
}
func (m *MockServer) Register() error {
return nil
}
func (m *MockServer) Deregister() error {
return nil
}
func (m *MockServer) Start() error {
m.Lock()
defer m.Unlock()
if m.Running {
return errors.New("already running")
}
m.Running = true
return nil
}
func (m *MockServer) Stop() error {
m.Lock()
defer m.Unlock()
if !m.Running {
return errors.New("not running")
}
m.Running = false
return nil
}
func (m *MockServer) String() string {
return "mock"
}
func NewServer(opts ...server.Option) *MockServer {
return newMockServer(opts...)
}
+28
View File
@@ -0,0 +1,28 @@
package mock
import (
"go-micro.dev/v6/registry"
"go-micro.dev/v6/server"
)
type MockHandler struct {
Opts server.HandlerOptions
Hdlr interface{}
Id string
}
func (m *MockHandler) Name() string {
return m.Id
}
func (m *MockHandler) Handler() interface{} {
return m.Hdlr
}
func (m *MockHandler) Endpoints() []*registry.Endpoint {
return []*registry.Endpoint{}
}
func (m *MockHandler) Options() server.HandlerOptions {
return m.Opts
}
+28
View File
@@ -0,0 +1,28 @@
package mock
import (
"go-micro.dev/v6/registry"
"go-micro.dev/v6/server"
)
type MockSubscriber struct {
Opts server.SubscriberOptions
Sub interface{}
Id string
}
func (m *MockSubscriber) Topic() string {
return m.Id
}
func (m *MockSubscriber) Subscriber() interface{} {
return m.Sub
}
func (m *MockSubscriber) Endpoints() []*registry.Endpoint {
return []*registry.Endpoint{}
}
func (m *MockSubscriber) Options() server.SubscriberOptions {
return m.Opts
}
+57
View File
@@ -0,0 +1,57 @@
package mock
import (
"testing"
"go-micro.dev/v6/server"
)
func TestMockServer(t *testing.T) {
srv := NewServer(
server.Name("mock"),
server.Version("latest"),
)
if srv.Options().Name != "mock" {
t.Fatalf("Expected name mock, got %s", srv.Options().Name)
}
if srv.Options().Version != "latest" {
t.Fatalf("Expected version latest, got %s", srv.Options().Version)
}
srv.Init(server.Version("test"))
if srv.Options().Version != "test" {
t.Fatalf("Expected version test, got %s", srv.Options().Version)
}
h := srv.NewHandler(func() string { return "foo" })
if err := srv.Handle(h); err != nil {
t.Fatal(err)
}
sub := srv.NewSubscriber("test", func() string { return "foo" })
if err := srv.Subscribe(sub); err != nil {
t.Fatal(err)
}
if sub.Topic() != "test" {
t.Fatalf("Expected topic test got %s", sub.Topic())
}
if err := srv.Start(); err != nil {
t.Fatal(err)
}
if err := srv.Register(); err != nil {
t.Fatal(err)
}
if err := srv.Deregister(); err != nil {
t.Fatal(err)
}
if err := srv.Stop(); err != nil {
t.Fatal(err)
}
}
+306
View File
@@ -0,0 +1,306 @@
package server
import (
"context"
"crypto/tls"
"sync"
"time"
"go-micro.dev/v6/broker"
"go-micro.dev/v6/codec"
"go-micro.dev/v6/debug/trace"
"go-micro.dev/v6/logger"
"go-micro.dev/v6/registry"
"go-micro.dev/v6/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)
}
}
+204
View File
@@ -0,0 +1,204 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: server.proto
package go_micro_server
import (
fmt "fmt"
proto "github.com/golang/protobuf/proto"
math "math"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
type HandleRequest struct {
Service string `protobuf:"bytes,1,opt,name=service,proto3" json:"service,omitempty"`
Endpoint string `protobuf:"bytes,2,opt,name=endpoint,proto3" json:"endpoint,omitempty"`
Protocol string `protobuf:"bytes,3,opt,name=protocol,proto3" json:"protocol,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *HandleRequest) Reset() { *m = HandleRequest{} }
func (m *HandleRequest) String() string { return proto.CompactTextString(m) }
func (*HandleRequest) ProtoMessage() {}
func (*HandleRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_ad098daeda4239f7, []int{0}
}
func (m *HandleRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_HandleRequest.Unmarshal(m, b)
}
func (m *HandleRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_HandleRequest.Marshal(b, m, deterministic)
}
func (m *HandleRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_HandleRequest.Merge(m, src)
}
func (m *HandleRequest) XXX_Size() int {
return xxx_messageInfo_HandleRequest.Size(m)
}
func (m *HandleRequest) XXX_DiscardUnknown() {
xxx_messageInfo_HandleRequest.DiscardUnknown(m)
}
var xxx_messageInfo_HandleRequest proto.InternalMessageInfo
func (m *HandleRequest) GetService() string {
if m != nil {
return m.Service
}
return ""
}
func (m *HandleRequest) GetEndpoint() string {
if m != nil {
return m.Endpoint
}
return ""
}
func (m *HandleRequest) GetProtocol() string {
if m != nil {
return m.Protocol
}
return ""
}
type HandleResponse struct {
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *HandleResponse) Reset() { *m = HandleResponse{} }
func (m *HandleResponse) String() string { return proto.CompactTextString(m) }
func (*HandleResponse) ProtoMessage() {}
func (*HandleResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_ad098daeda4239f7, []int{1}
}
func (m *HandleResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_HandleResponse.Unmarshal(m, b)
}
func (m *HandleResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_HandleResponse.Marshal(b, m, deterministic)
}
func (m *HandleResponse) XXX_Merge(src proto.Message) {
xxx_messageInfo_HandleResponse.Merge(m, src)
}
func (m *HandleResponse) XXX_Size() int {
return xxx_messageInfo_HandleResponse.Size(m)
}
func (m *HandleResponse) XXX_DiscardUnknown() {
xxx_messageInfo_HandleResponse.DiscardUnknown(m)
}
var xxx_messageInfo_HandleResponse proto.InternalMessageInfo
type SubscribeRequest struct {
Topic string `protobuf:"bytes,1,opt,name=topic,proto3" json:"topic,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *SubscribeRequest) Reset() { *m = SubscribeRequest{} }
func (m *SubscribeRequest) String() string { return proto.CompactTextString(m) }
func (*SubscribeRequest) ProtoMessage() {}
func (*SubscribeRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_ad098daeda4239f7, []int{2}
}
func (m *SubscribeRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_SubscribeRequest.Unmarshal(m, b)
}
func (m *SubscribeRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_SubscribeRequest.Marshal(b, m, deterministic)
}
func (m *SubscribeRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_SubscribeRequest.Merge(m, src)
}
func (m *SubscribeRequest) XXX_Size() int {
return xxx_messageInfo_SubscribeRequest.Size(m)
}
func (m *SubscribeRequest) XXX_DiscardUnknown() {
xxx_messageInfo_SubscribeRequest.DiscardUnknown(m)
}
var xxx_messageInfo_SubscribeRequest proto.InternalMessageInfo
func (m *SubscribeRequest) GetTopic() string {
if m != nil {
return m.Topic
}
return ""
}
type SubscribeResponse struct {
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *SubscribeResponse) Reset() { *m = SubscribeResponse{} }
func (m *SubscribeResponse) String() string { return proto.CompactTextString(m) }
func (*SubscribeResponse) ProtoMessage() {}
func (*SubscribeResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_ad098daeda4239f7, []int{3}
}
func (m *SubscribeResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_SubscribeResponse.Unmarshal(m, b)
}
func (m *SubscribeResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_SubscribeResponse.Marshal(b, m, deterministic)
}
func (m *SubscribeResponse) XXX_Merge(src proto.Message) {
xxx_messageInfo_SubscribeResponse.Merge(m, src)
}
func (m *SubscribeResponse) XXX_Size() int {
return xxx_messageInfo_SubscribeResponse.Size(m)
}
func (m *SubscribeResponse) XXX_DiscardUnknown() {
xxx_messageInfo_SubscribeResponse.DiscardUnknown(m)
}
var xxx_messageInfo_SubscribeResponse proto.InternalMessageInfo
func init() {
proto.RegisterType((*HandleRequest)(nil), "go.micro.server.HandleRequest")
proto.RegisterType((*HandleResponse)(nil), "go.micro.server.HandleResponse")
proto.RegisterType((*SubscribeRequest)(nil), "go.micro.server.SubscribeRequest")
proto.RegisterType((*SubscribeResponse)(nil), "go.micro.server.SubscribeResponse")
}
func init() { proto.RegisterFile("server.proto", fileDescriptor_ad098daeda4239f7) }
var fileDescriptor_ad098daeda4239f7 = []byte{
// 217 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x29, 0x4e, 0x2d, 0x2a,
0x4b, 0x2d, 0xd2, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x4f, 0xcf, 0xd7, 0xcb, 0xcd, 0x4c,
0x2e, 0xca, 0xd7, 0x83, 0x08, 0x2b, 0x25, 0x72, 0xf1, 0x7a, 0x24, 0xe6, 0xa5, 0xe4, 0xa4, 0x06,
0xa5, 0x16, 0x96, 0xa6, 0x16, 0x97, 0x08, 0x49, 0x70, 0xb1, 0x83, 0xa4, 0x32, 0x93, 0x53, 0x25,
0x18, 0x15, 0x18, 0x35, 0x38, 0x83, 0x60, 0x5c, 0x21, 0x29, 0x2e, 0x8e, 0xd4, 0xbc, 0x94, 0x82,
0xfc, 0xcc, 0xbc, 0x12, 0x09, 0x26, 0xb0, 0x14, 0x9c, 0x0f, 0x92, 0x03, 0x5b, 0x90, 0x9c, 0x9f,
0x23, 0xc1, 0x0c, 0x91, 0x83, 0xf1, 0x95, 0x04, 0xb8, 0xf8, 0x60, 0x56, 0x14, 0x17, 0xe4, 0xe7,
0x15, 0xa7, 0x2a, 0x69, 0x70, 0x09, 0x04, 0x97, 0x26, 0x15, 0x27, 0x17, 0x65, 0x26, 0xc1, 0xed,
0x15, 0xe1, 0x62, 0x2d, 0xc9, 0x2f, 0xc8, 0x4c, 0x86, 0xda, 0x0a, 0xe1, 0x28, 0x09, 0x73, 0x09,
0x22, 0xa9, 0x84, 0x68, 0x37, 0x5a, 0xcd, 0xc8, 0xc5, 0x16, 0x0c, 0x76, 0xbe, 0x90, 0x37, 0x17,
0x1b, 0xc4, 0x6c, 0x21, 0x39, 0x3d, 0x34, 0xaf, 0xe9, 0xa1, 0xf8, 0x4b, 0x4a, 0x1e, 0xa7, 0x3c,
0xd4, 0x51, 0x0c, 0x42, 0x21, 0x5c, 0x9c, 0x70, 0xcb, 0x84, 0x14, 0x31, 0xd4, 0xa3, 0x3b, 0x59,
0x4a, 0x09, 0x9f, 0x12, 0x98, 0xa9, 0x49, 0x6c, 0xe0, 0x80, 0x30, 0x06, 0x04, 0x00, 0x00, 0xff,
0xff, 0xe0, 0x77, 0x9a, 0xe4, 0x89, 0x01, 0x00, 0x00,
}
+102
View File
@@ -0,0 +1,102 @@
// Code generated by protoc-gen-micro. DO NOT EDIT.
// source: server.proto
package go_micro_server
import (
fmt "fmt"
proto "github.com/golang/protobuf/proto"
math "math"
)
import (
context "context"
client "go-micro.dev/v6/client"
server "go-micro.dev/v6/server"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ client.Option
var _ server.Option
// Client API for Server service
type ServerService interface {
Handle(ctx context.Context, in *HandleRequest, opts ...client.CallOption) (*HandleResponse, error)
Subscribe(ctx context.Context, in *SubscribeRequest, opts ...client.CallOption) (*SubscribeResponse, error)
}
type serverService struct {
c client.Client
name string
}
func NewServerService(name string, c client.Client) ServerService {
return &serverService{
c: c,
name: name,
}
}
func (c *serverService) Handle(ctx context.Context, in *HandleRequest, opts ...client.CallOption) (*HandleResponse, error) {
req := c.c.NewRequest(c.name, "Server.Handle", in)
out := new(HandleResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *serverService) Subscribe(ctx context.Context, in *SubscribeRequest, opts ...client.CallOption) (*SubscribeResponse, error) {
req := c.c.NewRequest(c.name, "Server.Subscribe", in)
out := new(SubscribeResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// Server API for Server service
type ServerHandler interface {
Handle(context.Context, *HandleRequest, *HandleResponse) error
Subscribe(context.Context, *SubscribeRequest, *SubscribeResponse) error
}
func RegisterServerHandler(s server.Server, hdlr ServerHandler, opts ...server.HandlerOption) error {
type server interface {
Handle(ctx context.Context, in *HandleRequest, out *HandleResponse) error
Subscribe(ctx context.Context, in *SubscribeRequest, out *SubscribeResponse) error
}
type Server struct {
server
}
h := &serverHandler{hdlr}
return s.Handle(s.NewHandler(&Server{h}, opts...))
}
type serverHandler struct {
ServerHandler
}
func (h *serverHandler) Handle(ctx context.Context, in *HandleRequest, out *HandleResponse) error {
return h.ServerHandler.Handle(ctx, in, out)
}
func (h *serverHandler) Subscribe(ctx context.Context, in *SubscribeRequest, out *SubscribeResponse) error {
return h.ServerHandler.Subscribe(ctx, in, out)
}
+22
View File
@@ -0,0 +1,22 @@
syntax = "proto3";
package go.micro.server;
service Server {
rpc Handle(HandleRequest) returns (HandleResponse) {};
rpc Subscribe(SubscribeRequest) returns (SubscribeResponse) {};
}
message HandleRequest {
string service = 1;
string endpoint = 2;
string protocol = 3;
}
message HandleResponse {}
message SubscribeRequest {
string topic = 1;
}
message SubscribeResponse {}
+362
View File
@@ -0,0 +1,362 @@
package server
import (
"bytes"
"sync"
"github.com/oxtoacart/bpool"
"github.com/pkg/errors"
"go-micro.dev/v6/codec"
raw "go-micro.dev/v6/codec/bytes"
"go-micro.dev/v6/codec/grpc"
"go-micro.dev/v6/codec/json"
"go-micro.dev/v6/codec/jsonrpc"
"go-micro.dev/v6/codec/proto"
"go-micro.dev/v6/codec/protorpc"
"go-micro.dev/v6/transport"
"go-micro.dev/v6/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
}
+109
View File
@@ -0,0 +1,109 @@
package server
import (
"bytes"
"errors"
"testing"
"go-micro.dev/v6/codec"
"go-micro.dev/v6/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
}
+39
View File
@@ -0,0 +1,39 @@
package server
import (
"go-micro.dev/v6/broker"
"go-micro.dev/v6/transport"
"go-micro.dev/v6/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,
},
}
}
+178
View File
@@ -0,0 +1,178 @@
package server
import (
"context"
"fmt"
"go-micro.dev/v6/broker"
raw "go-micro.dev/v6/codec/bytes"
log "go-micro.dev/v6/logger"
"go-micro.dev/v6/metadata"
"go-micro.dev/v6/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(subscriber string) func(e broker.Event) error {
return func(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
// create a wrapped function
handler := func(ctx context.Context, msg Message) error {
return s.opts.Router.ProcessMessage(ctx, subscriber, msg)
}
// 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: func(ctx context.Context, _ string, msg Message) error {
return handler(ctx, msg)
}}
}
return r.ProcessMessage(ctx, subscriber, 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 && s.subscriber == nil {
sub, err := s.opts.Broker.Subscribe(config.Name, s.HandleEvent(config.Name))
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) {
for sb := range s.subscribers {
if s.subscribers[sb] != nil {
continue
}
// If we've already created a broker subscription for this topic
// (from a different Subscriber entry) then don't create another
// broker.Subscribe. We still need to register the subscriber with
// the router so it receives dispatched messages.
var already bool
for other, subs := range s.subscribers {
if other.Topic() == sb.Topic() && subs != nil {
already = true
break
}
}
if already {
// register with router only
if err := s.router.Subscribe(sb); err != nil {
config.Logger.Logf(log.WarnLevel, "Unable to subscribing to topic: %s, error: %s", sb.Topic(), err)
continue
}
// mark this subscriber as having no broker subscription
s.subscribers[sb] = nil
continue
}
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(sb.Topic()), opts...)
if err != nil {
config.Logger.Logf(log.WarnLevel, "Unable to subscribing to topic: %s, error: %s", sb.Topic(), err)
continue
}
err = s.router.Subscribe(sb)
if err != nil {
config.Logger.Logf(log.WarnLevel, "Unable to subscribing to topic: %s, error: %s", sb.Topic(), err)
_ = sub.Unsubscribe()
continue
}
s.subscribers[sb] = []broker.Subscriber{sub}
}
}
+219
View File
@@ -0,0 +1,219 @@
package server
import (
"context"
"sync"
"sync/atomic"
"testing"
"time"
"go-micro.dev/v6/broker"
"go-micro.dev/v6/registry"
)
// TestSubscriberNoDuplicates verifies that when multiple subscribers are registered
// for the same topic with different queues, each handler is called exactly once
// per published message (no duplicate deliveries).
func TestSubscriberNoDuplicates(t *testing.T) {
// Create a memory broker
memBroker := broker.NewMemoryBroker()
if err := memBroker.Connect(); err != nil {
t.Fatalf("Failed to connect broker: %v", err)
}
defer memBroker.Disconnect()
// Create a memory registry
memRegistry := registry.NewMemoryRegistry()
// Create server with memory broker and registry
srv := NewRPCServer(
Broker(memBroker),
Registry(memRegistry),
Name("test.service"),
Id("test-1"),
Address("127.0.0.1:0"),
)
// Track handler invocations
var countA, countB, countC int32
// Handler functions
handlerA := func(ctx context.Context, msg *TestMessage) error {
atomic.AddInt32(&countA, 1)
return nil
}
handlerB := func(ctx context.Context, msg *TestMessage) error {
atomic.AddInt32(&countB, 1)
return nil
}
handlerC := func(ctx context.Context, msg *TestMessage) error {
atomic.AddInt32(&countC, 1)
return nil
}
// Register three subscribers with same topic but different queues
topic := "EVENT_1"
subA := srv.NewSubscriber(topic, handlerA, SubscriberQueue("A"))
if err := srv.Subscribe(subA); err != nil {
t.Fatalf("Failed to subscribe A: %v", err)
}
subB := srv.NewSubscriber(topic, handlerB, SubscriberQueue("B"))
if err := srv.Subscribe(subB); err != nil {
t.Fatalf("Failed to subscribe B: %v", err)
}
subC := srv.NewSubscriber(topic, handlerC, SubscriberQueue("C"))
if err := srv.Subscribe(subC); err != nil {
t.Fatalf("Failed to subscribe C: %v", err)
}
// Start the server (this will trigger reSubscribe)
if err := srv.Start(); err != nil {
t.Fatalf("Failed to start server: %v", err)
}
defer srv.Stop()
// Give server time to establish subscriptions
time.Sleep(100 * time.Millisecond)
// Publish a message to the topic
if err := memBroker.Publish(topic, &broker.Message{
Header: map[string]string{
"Micro-Topic": topic,
"Content-Type": "application/json",
},
Body: []byte(`{"value":"test"}`),
}); err != nil {
t.Fatalf("Failed to publish message: %v", err)
}
// Give handlers time to process
time.Sleep(200 * time.Millisecond)
// Verify each handler was called exactly once
if got := atomic.LoadInt32(&countA); got != 1 {
t.Errorf("Handler A called %d times, expected 1", got)
}
if got := atomic.LoadInt32(&countB); got != 1 {
t.Errorf("Handler B called %d times, expected 1", got)
}
if got := atomic.LoadInt32(&countC); got != 1 {
t.Errorf("Handler C called %d times, expected 1", got)
}
}
// TestSubscriberMultipleTopics verifies that subscribers for different topics
// each receive their respective messages correctly.
func TestSubscriberMultipleTopics(t *testing.T) {
// Create a memory broker
memBroker := broker.NewMemoryBroker()
if err := memBroker.Connect(); err != nil {
t.Fatalf("Failed to connect broker: %v", err)
}
defer memBroker.Disconnect()
// Create a memory registry
memRegistry := registry.NewMemoryRegistry()
// Create server
srv := NewRPCServer(
Broker(memBroker),
Registry(memRegistry),
Name("test.service"),
Id("test-2"),
Address("127.0.0.1:0"),
)
// Track handler invocations
var count1, count2 int32
var wg sync.WaitGroup
wg.Add(2)
// Handler functions
handler1 := func(ctx context.Context, msg *TestMessage) error {
atomic.AddInt32(&count1, 1)
wg.Done()
return nil
}
handler2 := func(ctx context.Context, msg *TestMessage) error {
atomic.AddInt32(&count2, 1)
wg.Done()
return nil
}
// Register subscribers for different topics
topic1 := "TOPIC_1"
topic2 := "TOPIC_2"
sub1 := srv.NewSubscriber(topic1, handler1)
if err := srv.Subscribe(sub1); err != nil {
t.Fatalf("Failed to subscribe to topic1: %v", err)
}
sub2 := srv.NewSubscriber(topic2, handler2)
if err := srv.Subscribe(sub2); err != nil {
t.Fatalf("Failed to subscribe to topic2: %v", err)
}
// Start the server
if err := srv.Start(); err != nil {
t.Fatalf("Failed to start server: %v", err)
}
defer srv.Stop()
// Give server time to establish subscriptions
time.Sleep(100 * time.Millisecond)
// Publish messages to different topics
if err := memBroker.Publish(topic1, &broker.Message{
Header: map[string]string{
"Micro-Topic": topic1,
"Content-Type": "application/json",
},
Body: []byte(`{"value":"test1"}`),
}); err != nil {
t.Fatalf("Failed to publish to topic1: %v", err)
}
if err := memBroker.Publish(topic2, &broker.Message{
Header: map[string]string{
"Micro-Topic": topic2,
"Content-Type": "application/json",
},
Body: []byte(`{"value":"test2"}`),
}); err != nil {
t.Fatalf("Failed to publish to topic2: %v", err)
}
// Wait for handlers to be called
done := make(chan struct{})
go func() {
wg.Wait()
close(done)
}()
select {
case <-done:
// Success
case <-time.After(2 * time.Second):
t.Fatal("Timeout waiting for handlers to be called")
}
// Verify each handler was called exactly once
if got := atomic.LoadInt32(&count1); got != 1 {
t.Errorf("Handler 1 called %d times, expected 1", got)
}
if got := atomic.LoadInt32(&count2); got != 1 {
t.Errorf("Handler 2 called %d times, expected 1", got)
}
}
// TestMessage is a test message type
type TestMessage struct {
Value string `json:"value"`
}
+83
View File
@@ -0,0 +1,83 @@
package server
import (
"reflect"
"go-micro.dev/v6/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()
// Auto-extract documentation from Go doc comments
autoMetadata := extractHandlerDocs(handler)
// Merge auto-extracted metadata with manually provided metadata
// Manual metadata takes precedence over auto-extracted
for endpoint, meta := range autoMetadata {
fullName := name + "." + endpoint
if options.Metadata[fullName] == nil {
options.Metadata[fullName] = make(map[string]string)
}
// Only add auto-extracted values if not manually provided
for k, v := range meta {
if _, exists := options.Metadata[fullName][k]; !exists {
options.Metadata[fullName][k] = v
}
}
}
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
}
+85
View File
@@ -0,0 +1,85 @@
package server
import (
"fmt"
"sync"
"go-micro.dev/v6/codec"
"go-micro.dev/v6/registry"
)
// 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
}
// 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/v6/codec"
"go-micro.dev/v6/internal/util/buf"
"go-micro.dev/v6/transport"
)
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/v6/codec"
"go-micro.dev/v6/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,
})
}
+658
View File
@@ -0,0 +1,658 @@
package server
import (
"context"
"errors"
"fmt"
"io"
"reflect"
"runtime/debug"
"strings"
"sync"
"unicode"
"unicode/utf8"
"go-micro.dev/v6/codec"
merrors "go-micro.dev/v6/errors"
log "go-micro.dev/v6/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
// 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, string, Message) error
}
func (r rpcRouter) ProcessMessage(ctx context.Context, subscriber string, msg Message) error {
return r.m(ctx, subscriber, 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.Pointer {
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.Pointer {
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.Pointer {
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, subscriber string, 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[subscriber]
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.Pointer {
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
}
+890
View File
@@ -0,0 +1,890 @@
package server
import (
"context"
"io"
"net"
"runtime/debug"
"sort"
"strconv"
"strings"
"sync"
"time"
"github.com/pkg/errors"
"go-micro.dev/v6/broker"
"go-micro.dev/v6/codec"
"go-micro.dev/v6/internal/util/addr"
"go-micro.dev/v6/internal/util/backoff"
mnet "go-micro.dev/v6/internal/util/net"
"go-micro.dev/v6/internal/util/socket"
log "go-micro.dev/v6/logger"
"go-micro.dev/v6/metadata"
"go-micro.dev/v6/registry"
"go-micro.dev/v6/transport"
"go-micro.dev/v6/transport/headers"
)
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.Topic())(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() {
closeConn = true
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() {
if r := recover(); r != nil {
logger.Log(log.ErrorLevel, "panic recovered in outbound goroutine: ", r)
logger.Log(log.ErrorLevel, string(debug.Stack()))
}
// 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 func() {
if r := recover(); r != nil {
logger.Log(log.ErrorLevel, "panic recovered in serveReq goroutine: ", r)
logger.Log(log.ErrorLevel, string(debug.Stack()))
}
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
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 regErr = config.Registry.Register(service, rOpts...); regErr != nil {
time.Sleep(backoff.Do(i + 1))
continue
}
break
}
if regErr != nil {
return regErr
}
s.Lock()
defer s.Unlock()
// 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
s.reSubscribe(config)
return nil
}
}
// 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/v6/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()
}
+133
View File
@@ -0,0 +1,133 @@
package server
import (
"bytes"
"fmt"
"io"
"math/rand"
"sync"
"testing"
"time"
"github.com/golang/protobuf/proto"
"go-micro.dev/v6/codec/json"
protoCodec "go-micro.dev/v6/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()
}
+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/v6/codec"
signalutil "go-micro.dev/v6/internal/util/signal"
log "go-micro.dev/v6/logger"
"go-micro.dev/v6/registry"
)
// 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, string, 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/v6/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