forked from gorilla/rpc
-
Notifications
You must be signed in to change notification settings - Fork 1
/
map.go
180 lines (165 loc) · 4.97 KB
/
map.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
// Copyright 2009 The Go Authors. All rights reserved.
// Copyright 2012 The Gorilla Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package rpc
import (
"fmt"
"net/http"
"reflect"
"strings"
"sync"
"unicode"
"unicode/utf8"
)
var (
// Precompute the reflect.Type of error and http.Request
typeOfError = reflect.TypeOf((*error)(nil)).Elem()
typeOfRequest = reflect.TypeOf((*http.Request)(nil)).Elem()
)
// ----------------------------------------------------------------------------
// service
// ----------------------------------------------------------------------------
type service struct {
name string // name of service
rcvr reflect.Value // receiver of methods for the service
rcvrType reflect.Type // type of the receiver
methods map[string]*serviceMethod // registered methods
passReq bool
}
type serviceMethod struct {
method reflect.Method // receiver method
argsType reflect.Type // type of the request argument
replyType reflect.Type // type of the response argument
}
// ----------------------------------------------------------------------------
// serviceMap
// ----------------------------------------------------------------------------
// serviceMap is a registry for services.
type serviceMap struct {
mutex sync.Mutex
services map[string]*service
}
// register adds a new service using reflection to extract its methods.
func (m *serviceMap) register(rcvr interface{}, name string, passReq bool) error {
// Setup service.
s := &service{
name: name,
rcvr: reflect.ValueOf(rcvr),
rcvrType: reflect.TypeOf(rcvr),
methods: make(map[string]*serviceMethod),
passReq: passReq,
}
if name == "" {
s.name = reflect.Indirect(s.rcvr).Type().Name()
if !isExported(s.name) {
return fmt.Errorf("rpc: type %q is not exported", s.name)
}
}
if s.name == "" {
return fmt.Errorf("rpc: no service name for type %q",
s.rcvrType.String())
}
// Setup methods.
for i := 0; i < s.rcvrType.NumMethod(); i++ {
method := s.rcvrType.Method(i)
mtype := method.Type
// offset the parameter indexes by one if the
// service methods accept an HTTP request pointer
var paramOffset int
if passReq {
paramOffset = 1
} else {
paramOffset = 0
}
// Method must be exported.
if method.PkgPath != "" {
continue
}
// Method needs four ins: receiver, *http.Request, *args, *reply.
if mtype.NumIn() != 3+paramOffset {
continue
}
// If the service methods accept an HTTP request pointer
if passReq {
// First argument must be a pointer and must be http.Request.
reqType := mtype.In(1)
if reqType.Kind() != reflect.Ptr || reqType.Elem() != typeOfRequest {
continue
}
}
// Next argument must be a pointer and must be exported.
args := mtype.In(1 + paramOffset)
if args.Kind() != reflect.Ptr || !isExportedOrBuiltin(args) {
continue
}
// Next argument must be a pointer and must be exported.
reply := mtype.In(2 + paramOffset)
if reply.Kind() != reflect.Ptr || !isExportedOrBuiltin(reply) {
continue
}
// Method needs one out: error.
if mtype.NumOut() != 1 {
continue
}
if returnType := mtype.Out(0); returnType != typeOfError {
continue
}
s.methods[method.Name] = &serviceMethod{
method: method,
argsType: args.Elem(),
replyType: reply.Elem(),
}
}
if len(s.methods) == 0 {
return fmt.Errorf("rpc: %q has no exported methods of suitable type",
s.name)
}
// Add to the map.
m.mutex.Lock()
defer m.mutex.Unlock()
if m.services == nil {
m.services = make(map[string]*service)
} else if _, ok := m.services[s.name]; ok {
return fmt.Errorf("rpc: service already defined: %q", s.name)
}
m.services[s.name] = s
return nil
}
// get returns a registered service given a method name.
//
// The method name uses a dotted notation as in "Service.Method".
func (m *serviceMap) get(method string) (*service, *serviceMethod, error) {
parts := strings.Split(method, ".")
if len(parts) != 2 {
err := fmt.Errorf("rpc: service/method request ill-formed: %q", method)
return nil, nil, err
}
m.mutex.Lock()
service := m.services[parts[0]]
m.mutex.Unlock()
if service == nil {
err := fmt.Errorf("rpc: can't find service %q", method)
return nil, nil, err
}
serviceMethod := service.methods[parts[1]]
if serviceMethod == nil {
err := fmt.Errorf("rpc: can't find method %q", method)
return nil, nil, err
}
return service, serviceMethod, nil
}
// isExported returns true of a string is an exported (upper case) name.
func isExported(name string) bool {
rune, _ := utf8.DecodeRuneInString(name)
return unicode.IsUpper(rune)
}
// isExportedOrBuiltin returns true if a type is exported or a builtin.
func isExportedOrBuiltin(t reflect.Type) bool {
for t.Kind() == reflect.Ptr {
t = t.Elem()
}
// PkgPath will be non-empty even for an exported type,
// so we need to check the type name as well.
return isExported(t.Name()) || t.PkgPath() == ""
}