forked from creachadair/jrpc2
-
Notifications
You must be signed in to change notification settings - Fork 0
/
base.go
250 lines (224 loc) · 8.52 KB
/
base.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
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
// Copyright (C) 2017 Michael J. Fromberger. All Rights Reserved.
package jrpc2
import (
"bytes"
"context"
"encoding/json"
"fmt"
"strings"
"github.com/creachadair/jrpc2/code"
)
// An Assigner assigns a Handler to handle the specified method name, or nil if
// no method is available to handle the request.
type Assigner interface {
// Assign returns the handler for the named method, or nil.
// The implementation can obtain the complete request from ctx using the
// jrpc2.InboundRequest function.
Assign(ctx context.Context, method string) Handler
}
// Namer is an optional interface that an Assigner may implement to expose the
// names of its methods to the ServerInfo method.
type Namer interface {
// Names returns all known method names in lexicographic order.
Names() []string
}
// A Handler handles a single request.
type Handler interface {
// Handle invokes the method with the specified request. The response value
// must be JSON-marshalable or nil. In case of error, the handler can
// return a value of type *jrpc2.Error to control the response code sent
// back to the caller; otherwise the server will wrap the resulting value.
//
// The context passed to the handler by a *jrpc2.Server includes two special
// values that the handler may extract.
//
// To obtain the server instance running the handler, write:
//
// srv := jrpc2.ServerFromContext(ctx)
//
// To obtain the inbound request message, write:
//
// req := jrpc2.InboundRequest(ctx)
//
// The latter is primarily useful for handlers generated by handler.New,
// which do not receive the request directly. For a handler that implements
// the Handle method directly, req is the same value passed as a parameter
// to Handle.
Handle(context.Context, *Request) (interface{}, error)
}
// A Request is a request message from a client to a server.
type Request struct {
id json.RawMessage // the request ID, nil for notifications
method string // the name of the method being requested
params json.RawMessage // method parameters
}
// IsNotification reports whether the request is a notification, and thus does
// not require a value response.
func (r *Request) IsNotification() bool { return r.id == nil }
// ID returns the request identifier for r, or "" if r is a notification.
func (r *Request) ID() string { return string(r.id) }
// Method reports the method name for the request.
func (r *Request) Method() string { return r.method }
// HasParams reports whether the request has non-empty parameters.
func (r *Request) HasParams() bool { return len(r.params) != 0 }
// UnmarshalParams decodes the request parameters of r into v. If r has empty
// parameters, it returns nil without modifying v. If the parameters are
// invalid, UnmarshalParams returns an InvalidParams error.
//
// By default, unknown object keys are ignored and discarded when unmarshaling
// into a v of struct type. If the type of v implements a DisallowUnknownFields
// method, unknown fields will instead generate an InvalidParams error. The
// jrpc2.StrictFields helper adapts existing struct values to this interface.
// For more specific behaviour, implement a custom json.Unmarshaler.
//
// If v has type *json.RawMessage, unmarshaling will never report an error.
func (r *Request) UnmarshalParams(v interface{}) error {
if len(r.params) == 0 {
return nil
}
switch t := v.(type) {
case *json.RawMessage:
*t = json.RawMessage(string(r.params)) // copy
return nil
case strictFielder:
dec := json.NewDecoder(bytes.NewReader(r.params))
dec.DisallowUnknownFields()
if err := dec.Decode(v); err != nil {
return errInvalidParams.WithData(err.Error())
}
return nil
}
if err := json.Unmarshal(r.params, v); err != nil {
return errInvalidParams.WithData(err.Error())
}
return nil
}
// ParamString returns the encoded request parameters of r as a string.
// If r has no parameters, it returns "".
func (r *Request) ParamString() string { return string(r.params) }
// A Response is a response message from a server to a client.
type Response struct {
id string
err *Error
result json.RawMessage
// Waiters synchronize on reading from ch. The first successful reader from
// ch completes the request and is responsible for updating rsp and then
// closing ch. The client owns writing to ch, and is responsible to ensure
// that at most one write is ever performed.
ch chan *jmessage
cancel func()
}
// ID returns the request identifier for r.
func (r *Response) ID() string { return r.id }
// SetID sets the ID of r to s, for use in proxies.
func (r *Response) SetID(s string) { r.id = s }
// Error returns a non-nil *Error if the response contains an error.
func (r *Response) Error() *Error { return r.err }
// UnmarshalResult decodes the result message into v. If the request failed,
// UnmarshalResult returns the same *Error value that is returned by r.Error(),
// and v is unmodified.
//
// By default, unknown object keys are ignored and discarded when unmarshaling
// into a v of struct type. If the type of v implements a DisallowUnknownFields
// method, unknown fields will instead generate an error. The
// jrpc2.StrictFields helper adapts existing struct values to this interface.
// For more specific behaviour, implement a custom json.Unmarshaler.
//
// If v has type *json.RawMessage, unmarshaling will never report an error.
func (r *Response) UnmarshalResult(v interface{}) error {
if r.err != nil {
return r.err
}
switch t := v.(type) {
case *json.RawMessage:
*t = json.RawMessage(string(r.result)) // copy
return nil
case strictFielder:
dec := json.NewDecoder(bytes.NewReader(r.result))
dec.DisallowUnknownFields()
return dec.Decode(v)
}
return json.Unmarshal(r.result, v)
}
// ResultString returns the encoded result message of r as a string.
// If r has no result, for example if r is an error response, it returns "".
func (r *Response) ResultString() string { return string(r.result) }
// MarshalJSON converts the response to equivalent JSON.
func (r *Response) MarshalJSON() ([]byte, error) {
return (&jmessage{
ID: json.RawMessage(r.id),
R: r.result,
E: r.err,
}).toJSON()
}
// wait blocks until r is complete. It is safe to call this multiple times and
// from concurrent goroutines.
func (r *Response) wait() {
raw, ok := <-r.ch
if ok {
// N.B. We intentionally DO NOT have the sender close the channel, to
// prevent a data race between callers of Wait. The channel is closed
// by the first waiter to get a real value (ok == true).
//
// The first waiter must update the response value, THEN close the
// channel and cancel the context. This order ensures that subsequent
// waiters all get the same response, and do not race on accessing it.
r.err = raw.E
r.result = raw.R
close(r.ch)
r.cancel() // release the context observer
// Safety check: The response IDs should match. Do this after delivery so
// a failure does not orphan resources.
if id := string(fixID(raw.ID)); id != r.id {
panic(fmt.Sprintf("Mismatched response ID %q expecting %q", id, r.id))
}
}
}
// Network guesses a network type for the specified address and returns a tuple
// of that type and the address.
//
// The assignment of a network type uses the following heuristics:
//
// If s does not have the form [host]:port, the network is assigned as "unix".
// The network "unix" is also assigned if port == "", port contains characters
// other than ASCII letters, digits, and "-", or if host contains a "/".
//
// Otherwise, the network is assigned as "tcp". Note that this function does
// not verify whether the address is lexically valid.
func Network(s string) (network, address string) {
i := strings.LastIndex(s, ":")
if i < 0 {
return "unix", s
}
host, port := s[:i], s[i+1:]
if port == "" || !isServiceName(port) {
return "unix", s
} else if strings.IndexByte(host, '/') >= 0 {
return "unix", s
}
return "tcp", s
}
// isServiceName reports whether s looks like a legal service name from the
// services(5) file. The grammar of such names is not well-defined, but for our
// purposes it includes letters, digits, and "-".
func isServiceName(s string) bool {
for i := range s {
b := s[i]
if b >= '0' && b <= '9' || b >= 'A' && b <= 'Z' || b >= 'a' && b <= 'z' || b == '-' {
continue
}
return false
}
return true
}
// filterError filters an *Error value to distinguish context errors from other
// error types. If err is not a context error, it is returned unchanged.
func filterError(e *Error) error {
switch e.Code {
case code.Cancelled:
return context.Canceled
case code.DeadlineExceeded:
return context.DeadlineExceeded
}
return e
}