251 lines
8.5 KiB
Go
251 lines
8.5 KiB
Go
// 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
|
|
}
|