~rogpeppe/juju-core/438-local-instance-Addresses

« back to all changes in this revision

Viewing changes to rpc/rpc.go

  • Committer: Roger Peppe
  • Date: 2012-12-03 18:42:13 UTC
  • mto: (841.1.2 197-api-passwords)
  • mto: This revision was merged to the branch mainline in revision 883.
  • Revision ID: roger.peppe@canonical.com-20121203184213-9getgnjiipl2u2hm
rpc: initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
package rpc
 
2
import (
 
3
        "fmt"
 
4
        "errors"
 
5
        "strings"
 
6
        "path"
 
7
        "reflect"
 
8
)
 
9
 
 
10
/*
 
11
Things to think about:
 
12
 
 
13
can we provide some way of distinguishing GET from POST methods?
 
14
*/
 
15
 
 
16
var errorType = reflect.TypeOf((*error)(nil)).Elem()
 
17
var errNilDereference = errors.New("field retrieval from nil reference")
 
18
 
 
19
type Server struct {
 
20
        root reflect.Value
 
21
        ctxtType reflect.Type
 
22
        // We store the member names for each type,
 
23
        // each one holding a function that can get the
 
24
        // field or method from its parent value.
 
25
        types map[reflect.Type] map[string] *procedure
 
26
}
 
27
 
 
28
// NewServer returns a new server that serves RPC requests by querying
 
29
// the given root value.  The request path specifies an object to act
 
30
// on.  The first element acts on the root value; each subsequent
 
31
// element acts on the value returned by the previous.  The value
 
32
// returned by the final element of the path is returned as the result
 
33
// of the RPC.  If any element in the path returns an error, evaluation
 
34
// stops there.
 
35
//
 
36
// A element of the path can specify the name of exported field or
 
37
// method. To be considered, a method must be defined
 
38
// in one of the following forms, where T and R represent
 
39
// arbitrary types (except the built-in error type):
 
40
//
 
41
//     Method() R
 
42
//     Method() (R, error)
 
43
//      Method(T) R
 
44
//      Method(T) (R, error)
 
45
//      Method()
 
46
//      Method() error
 
47
//      Method(T) error
 
48
//
 
49
// For any element in the path except the final one, the latter three
 
50
// forms may not be used, to ensure there is something for the next
 
51
// element to operate on; also in this case, the argument type T must be
 
52
// of type string - the path element must contain a hyphen (-) character
 
53
// and the argument to the method is supplied from any characters after
 
54
// that.
 
55
//
 
56
// For the last element in the path, the method argument will be filled
 
57
// in from the parameters passed to the RPC; the R result will be
 
58
// returned to the RPC caller.
 
59
//
 
60
// If the root value implements the method CheckContext, it will be
 
61
// called for any new connection before any other method, with the
 
62
// argument passed to ServeConn.  In this case, methods with two
 
63
// arguments are also considered - the first argument must be the same
 
64
// type as CheckContext's argument, and it will likewise be passed the
 
65
// context value given to ServerConn.
 
66
//
 
67
func NewServer(root interface{}) (*Server, error) {
 
68
        srv := &Server{
 
69
                root: reflect.ValueOf(root),
 
70
                types: make(map[reflect.Type] map[string] *procedure),
 
71
        }
 
72
        t := reflect.TypeOf(root)
 
73
        if m, ok := t.MethodByName("CheckContext"); ok {
 
74
                if m.Type.NumIn() != 2 ||
 
75
                        m.Type.NumOut() != 1 ||
 
76
                        m.Type.Out(0) != errorType {
 
77
                        return nil, fmt.Errorf("CheckContext has unexpected type %v", m.Type)
 
78
                }
 
79
                srv.ctxtType = m.Type.In(1)
 
80
        }
 
81
        srv.buildTypes(t)
 
82
        return srv, nil
 
83
}
 
84
 
 
85
type procedure struct {
 
86
        arg, ret reflect.Type
 
87
        call func(rcvr, ctxt, arg reflect.Value) (reflect.Value, error)
 
88
}
 
89
 
 
90
func (srv *Server) buildTypes(t reflect.Type) {
 
91
        if _, ok := srv.types[t]; ok {
 
92
                return
 
93
        }
 
94
        members := make(map[string] *procedure)
 
95
        // Add first to guard against infinite recursion.
 
96
        srv.types[t] = members
 
97
        for i := 0; i < t.NumMethod(); i++ {
 
98
                m := t.Method(i)
 
99
                if p := srv.methodToProcedure(m); p != nil {
 
100
                        members[m.Name] = p
 
101
                }
 
102
        }
 
103
        if t.Kind() != reflect.Struct {
 
104
                return
 
105
        }
 
106
        for i := 0; i < t.NumField(); i++ {
 
107
                f := t.Field(i)
 
108
                // TODO if t is addressable, use pointer to field so that we get pointer methods?
 
109
                if p := fieldToProcedure(t, f, i); p != nil {
 
110
                        members[f.Name] = p
 
111
                }
 
112
        }
 
113
        for _, m := range members {
 
114
                if m.ret != nil {
 
115
                        srv.buildTypes(m.ret)
 
116
                }
 
117
        }
 
118
}
 
119
 
 
120
func fieldToProcedure(rcvr reflect.Type, f reflect.StructField, index int) *procedure {
 
121
        if f.PkgPath != "" {
 
122
                return nil
 
123
        }
 
124
        var p procedure
 
125
        p.ret = f.Type
 
126
        if rcvr.Kind() == reflect.Ptr {
 
127
                p.call = func(rcvr, ctxt, arg reflect.Value) (r reflect.Value, err error) {
 
128
                        if rcvr.IsNil() {
 
129
                                err = errNilDereference
 
130
                                return
 
131
                        }
 
132
                        rcvr = rcvr.Elem()
 
133
                        return rcvr.Field(index), nil
 
134
                }
 
135
        } else {
 
136
                p.call = func(rcvr, ctxt, arg reflect.Value) (r reflect.Value, err error) {
 
137
                        return rcvr.Field(index), nil
 
138
                }
 
139
        }
 
140
        return &p
 
141
}
 
142
 
 
143
func (srv *Server) methodToProcedure(m reflect.Method) *procedure {
 
144
        if m.PkgPath != "" || m.Name == "CheckContext" {
 
145
                return nil
 
146
        }
 
147
        var p procedure
 
148
        var assemble func(ctxt, arg reflect.Value) []reflect.Value
 
149
        // N.B. The method type includes receiver as first argument.
 
150
        t := m.Type
 
151
        switch {
 
152
        case t.NumIn() == 1:
 
153
                assemble = func(ctxt, arg reflect.Value) []reflect.Value {
 
154
                        return nil
 
155
                }
 
156
        case t.NumIn() == 2 && t.In(1) == srv.ctxtType:
 
157
                assemble = func(ctxt, arg reflect.Value) []reflect.Value {
 
158
                        return []reflect.Value{ctxt}
 
159
                }
 
160
        case t.NumIn() == 3:
 
161
                p.arg = t.In(1)
 
162
                assemble = func(ctxt, arg reflect.Value) []reflect.Value {
 
163
                        return []reflect.Value{arg}
 
164
                }
 
165
        case t.NumIn() == 3 && t.In(1) == srv.ctxtType:
 
166
                p.arg = t.In(2)
 
167
                assemble = func(ctxt, arg reflect.Value) []reflect.Value {
 
168
                        return []reflect.Value{ctxt, arg}
 
169
                }
 
170
        default:
 
171
                return nil
 
172
        }
 
173
 
 
174
        switch {
 
175
        case t.NumOut() == 0:
 
176
                p.call = func(rcvr, ctxt, arg reflect.Value) (r reflect.Value, err error) {
 
177
                        rcvr.Method(m.Index).Call(assemble(ctxt, arg))
 
178
                        return
 
179
                }
 
180
        case t.NumOut() == 1 && t.Out(0) == errorType:
 
181
                p.call = func(rcvr, ctxt, arg reflect.Value) (r reflect.Value, err error) {
 
182
                        out := rcvr.Method(m.Index).Call(assemble(ctxt, arg))
 
183
                        if !out[0].IsNil() {
 
184
                                err = out[0].Interface().(error)
 
185
                        }
 
186
                        return
 
187
                }
 
188
        case t.NumOut() == 1:
 
189
                p.ret = t.Out(0)
 
190
                p.call = func(rcvr, ctxt, arg reflect.Value) (reflect.Value, error) {
 
191
                        out := rcvr.Method(m.Index).Call(assemble(ctxt, arg))
 
192
                        return out[0], nil
 
193
                }
 
194
        case t.NumOut() == 2 && t.Out(1) == errorType:
 
195
                p.ret = t.Out(0)
 
196
                p.call = func(rcvr, ctxt, arg reflect.Value) (r reflect.Value, err error) {
 
197
                        out := rcvr.Method(m.Index).Call(assemble(ctxt, arg))
 
198
                        r = out[0]
 
199
                        if !out[1].IsNil() {
 
200
                                err = out[0].Interface().(error)
 
201
                        }
 
202
                        return
 
203
                }
 
204
        default:
 
205
                return nil
 
206
        }
 
207
        return &p
 
208
}
 
209
 
 
210
type ServerCodec interface {
 
211
        // Auth is called once only on a given server;
 
212
        // it fetches the authentication data into req.
 
213
        Auth(req interface{}) error
 
214
        ReadRequestHeader(*Request) error
 
215
        ReadRequestBody(interface{}) error
 
216
        WriteResponse(*Response, interface{}) error
 
217
        Close() error
 
218
}
 
219
 
 
220
type Request struct {
 
221
        Path string
 
222
        Seq uint64
 
223
}
 
224
 
 
225
type Response struct {
 
226
    ServiceMethod string // echoes that of the Request
 
227
    Seq           uint64 // echoes that of the request
 
228
    Error         string // error, if any.
 
229
}
 
230
 
 
231
func (*Server) ServeConn(codec ServerCodec, ctxt interface{}) {
 
232
        
 
233
}
 
234
 
 
235
type pathError struct {
 
236
        reason string
 
237
        elems []string
 
238
}
 
239
 
 
240
func (e *pathError) Error() string {
 
241
        return fmt.Sprintf("error at %q: %v", path.Join(e.elems...), e.reason)
 
242
}
 
243
 
 
244
func (srv *Server) Call(path string, ctxt interface{}, arg reflect.Value) (reflect.Value, error) {
 
245
        elems := strings.FieldsFunc(path, func(r rune) bool { return r == '/' })
 
246
        v := srv.root
 
247
        for i, e := range elems {
 
248
                members, ok := srv.types[v.Type()]
 
249
                if !ok {
 
250
                        panic(fmt.Errorf("type %T not found", v))
 
251
                }
 
252
                hyphen := strings.Index(e, "-")
 
253
                var pathArg string
 
254
                if hyphen > 0 {
 
255
                        pathArg = e[hyphen+1:]
 
256
                        e = e[0:hyphen]
 
257
                }
 
258
                soFar := elems[0:i+1]
 
259
                p, ok := members[e]
 
260
                if !ok {
 
261
                        return reflect.Value{}, &pathError{"not found", soFar}
 
262
                }
 
263
                isLast := i == len(elems)-1
 
264
                if p.ret == nil && !isLast {
 
265
                        return reflect.Value{}, &pathError{"extra path elements", soFar}
 
266
                }
 
267
                var parg reflect.Value
 
268
                if hyphen > 0 {
 
269
                        if p.arg != nil || p.arg != reflect.TypeOf("") {
 
270
                                return reflect.Value{}, &pathError{"string argument given for inappropriate method/field", soFar}
 
271
                        }
 
272
                        if isLast && arg.IsValid() {
 
273
                                return reflect.Value{}, &pathError{"argument clash (path string vs arg)", soFar}
 
274
                        }
 
275
                        parg = reflect.ValueOf(pathArg)
 
276
                } else if isLast {
 
277
                        parg = arg
 
278
                }
 
279
                r, err := p.call(v, reflect.ValueOf(ctxt), parg)
 
280
                if err != nil {
 
281
                        if isLast {
 
282
                                return reflect.Value{}, err
 
283
                        }
 
284
                        return reflect.Value{}, &pathError{err.Error(), soFar}
 
285
                }
 
286
                v = r
 
287
        }
 
288
        return v, nil
 
289
}