mirror of
https://github.com/golang/go
synced 2024-11-18 10:24:42 -07:00
internal/jsonrpc2: a basic json rpc library to build an lsp on top of
Change-Id: I6aa47fffcb29842e3194231e4ad4b6be4386d329 Reviewed-on: https://go-review.googlesource.com/136675 Reviewed-by: Rebecca Stambler <rstambler@golang.org>
This commit is contained in:
parent
90fa682c2a
commit
e93be7f42f
341
internal/jsonrpc2/jsonrpc2.go
Normal file
341
internal/jsonrpc2/jsonrpc2.go
Normal file
@ -0,0 +1,341 @@
|
|||||||
|
// Copyright 2018 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.
|
||||||
|
|
||||||
|
// Package jsonrpc2 is a minimal implementation of the JSON RPC 2 spec.
|
||||||
|
// https://www.jsonrpc.org/specification
|
||||||
|
// It is intended to be compatible with other implementations at the wire level.
|
||||||
|
package jsonrpc2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Conn is a JSON RPC 2 client server connection.
|
||||||
|
// Conn is bidirectional; it does not have a designated server or client end.
|
||||||
|
type Conn struct {
|
||||||
|
handle Handler
|
||||||
|
cancel Canceler
|
||||||
|
log Logger
|
||||||
|
stream Stream
|
||||||
|
done chan struct{}
|
||||||
|
err error
|
||||||
|
seq int64 // must only be accessed using atomic operations
|
||||||
|
pendingMu sync.Mutex // protects the pending map
|
||||||
|
pending map[ID]chan *Response
|
||||||
|
handlingMu sync.Mutex // protects the handling map
|
||||||
|
handling map[ID]context.CancelFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handler is an option you can pass to NewConn to handle incoming requests.
|
||||||
|
// If the request returns true from IsNotify then the Handler should not return a
|
||||||
|
// result or error, otherwise it should handle the Request and return either
|
||||||
|
// an encoded result, or an error.
|
||||||
|
// Handlers must be concurrency-safe.
|
||||||
|
type Handler = func(context.Context, *Conn, *Request) (interface{}, *Error)
|
||||||
|
|
||||||
|
// Canceler is an option you can pass to NewConn which is invoked for
|
||||||
|
// cancelled outgoing requests.
|
||||||
|
// The request will have the ID filled in, which can be used to propagate the
|
||||||
|
// cancel to the other process if needed.
|
||||||
|
// It is okay to use the connection to send notifications, but the context will
|
||||||
|
// be in the cancelled state, so you must do it with the background context
|
||||||
|
// instead.
|
||||||
|
type Canceler = func(context.Context, *Conn, *Request)
|
||||||
|
|
||||||
|
// Logger is an option you can pass to NewConn which is invoked for
|
||||||
|
// all messages flowing through a Conn.
|
||||||
|
type Logger = func(mode string, id *ID, method string, payload *json.RawMessage, err *Error)
|
||||||
|
|
||||||
|
// NewErrorf builds a Error struct for the suppied message and code.
|
||||||
|
// If args is not empty, message and args will be passed to Sprintf.
|
||||||
|
func NewErrorf(code int64, format string, args ...interface{}) *Error {
|
||||||
|
return &Error{
|
||||||
|
Code: code,
|
||||||
|
Message: fmt.Sprintf(format, args...),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewConn creates a new connection object that reads and writes messages from
|
||||||
|
// the supplied stream and dispatches incoming messages to the supplied handler.
|
||||||
|
func NewConn(ctx context.Context, s Stream, options ...interface{}) *Conn {
|
||||||
|
conn := &Conn{
|
||||||
|
stream: s,
|
||||||
|
done: make(chan struct{}),
|
||||||
|
pending: make(map[ID]chan *Response),
|
||||||
|
handling: make(map[ID]context.CancelFunc),
|
||||||
|
}
|
||||||
|
for _, opt := range options {
|
||||||
|
switch opt := opt.(type) {
|
||||||
|
case Handler:
|
||||||
|
if conn.handle != nil {
|
||||||
|
panic("Duplicate Handler function in options list")
|
||||||
|
}
|
||||||
|
conn.handle = opt
|
||||||
|
case Canceler:
|
||||||
|
if conn.cancel != nil {
|
||||||
|
panic("Duplicate Canceler function in options list")
|
||||||
|
}
|
||||||
|
conn.cancel = opt
|
||||||
|
case Logger:
|
||||||
|
if conn.log != nil {
|
||||||
|
panic("Duplicate Logger function in options list")
|
||||||
|
}
|
||||||
|
conn.log = opt
|
||||||
|
default:
|
||||||
|
panic(fmt.Errorf("Unknown option type %T in options list", opt))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if conn.handle == nil {
|
||||||
|
// the default handler reports a method error
|
||||||
|
conn.handle = func(ctx context.Context, c *Conn, r *Request) (interface{}, *Error) {
|
||||||
|
return nil, NewErrorf(CodeMethodNotFound, "method %q not found", r.Method)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if conn.cancel == nil {
|
||||||
|
// the default canceller does nothing
|
||||||
|
conn.cancel = func(context.Context, *Conn, *Request) {}
|
||||||
|
}
|
||||||
|
if conn.log == nil {
|
||||||
|
// the default logger does nothing
|
||||||
|
conn.log = func(string, *ID, string, *json.RawMessage, *Error) {}
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
conn.err = conn.run(ctx)
|
||||||
|
close(conn.done)
|
||||||
|
}()
|
||||||
|
return conn
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait blocks until the connection is terminated, and returns any error that
|
||||||
|
// cause the termination.
|
||||||
|
func (c *Conn) Wait(ctx context.Context) error {
|
||||||
|
select {
|
||||||
|
case <-c.done:
|
||||||
|
return c.err
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cancel cancels a pending Call on the server side.
|
||||||
|
// The call is identified by its id.
|
||||||
|
// JSON RPC 2 does not specify a cancel message, so cancellation support is not
|
||||||
|
// directly wired in. This method allows a higher level protocol to choose how
|
||||||
|
// to propagate the cancel.
|
||||||
|
func (c *Conn) Cancel(id ID) {
|
||||||
|
c.handlingMu.Lock()
|
||||||
|
cancel := c.handling[id]
|
||||||
|
c.handlingMu.Unlock()
|
||||||
|
if cancel != nil {
|
||||||
|
cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify is called to send a notification request over the connection.
|
||||||
|
// It will return as soon as the notification has been sent, as no response is
|
||||||
|
// possible.
|
||||||
|
func (c *Conn) Notify(ctx context.Context, method string, params interface{}) error {
|
||||||
|
jsonParams, err := marshalToRaw(params)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("marshalling notify parameters: %v", err)
|
||||||
|
}
|
||||||
|
request := &Request{
|
||||||
|
Method: method,
|
||||||
|
Params: jsonParams,
|
||||||
|
}
|
||||||
|
data, err := json.Marshal(request)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("marshalling notify request: %v", err)
|
||||||
|
}
|
||||||
|
c.log("notify <=", nil, request.Method, request.Params, nil)
|
||||||
|
return c.stream.Write(ctx, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call sends a request over the connection and then waits for a response.
|
||||||
|
// If the response is not an error, it will be decoded into result.
|
||||||
|
// result must be of a type you an pass to json.Unmarshal.
|
||||||
|
func (c *Conn) Call(ctx context.Context, method string, params, result interface{}) error {
|
||||||
|
jsonParams, err := marshalToRaw(params)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("marshalling call parameters: %v", err)
|
||||||
|
}
|
||||||
|
// generate a new request identifier
|
||||||
|
id := ID{Number: atomic.AddInt64(&c.seq, 1)}
|
||||||
|
request := &Request{
|
||||||
|
ID: &id,
|
||||||
|
Method: method,
|
||||||
|
Params: jsonParams,
|
||||||
|
}
|
||||||
|
// marshal the request now it is complete
|
||||||
|
data, err := json.Marshal(request)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("marshalling call request: %v", err)
|
||||||
|
}
|
||||||
|
// we have to add ourselves to the pending map before we send, otherwise we
|
||||||
|
// are racing the response
|
||||||
|
rchan := make(chan *Response)
|
||||||
|
c.pendingMu.Lock()
|
||||||
|
c.pending[id] = rchan
|
||||||
|
c.pendingMu.Unlock()
|
||||||
|
defer func() {
|
||||||
|
// clean up the pending response handler on the way out
|
||||||
|
c.pendingMu.Lock()
|
||||||
|
delete(c.pending, id)
|
||||||
|
c.pendingMu.Unlock()
|
||||||
|
}()
|
||||||
|
// now we are ready to send
|
||||||
|
c.log("call <=", request.ID, request.Method, request.Params, nil)
|
||||||
|
if err := c.stream.Write(ctx, data); err != nil {
|
||||||
|
// sending failed, we will never get a response, so don't leave it pending
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// now wait for the response
|
||||||
|
select {
|
||||||
|
case response := <-rchan:
|
||||||
|
// is it an error response?
|
||||||
|
if response.Error != nil {
|
||||||
|
return response.Error
|
||||||
|
}
|
||||||
|
if result == nil || response.Result == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(*response.Result, result); err != nil {
|
||||||
|
return fmt.Errorf("unmarshalling result: %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
case <-ctx.Done():
|
||||||
|
// allow the handler to propagate the cancel
|
||||||
|
c.cancel(ctx, c, request)
|
||||||
|
return ctx.Err()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// combined has all the fields of both Request and Response.
|
||||||
|
// We can decode this and then work out which it is.
|
||||||
|
type combined struct {
|
||||||
|
VersionTag VersionTag `json:"jsonrpc"`
|
||||||
|
ID *ID `json:"id,omitempty"`
|
||||||
|
Method string `json:"method"`
|
||||||
|
Params *json.RawMessage `json:"params,omitempty"`
|
||||||
|
Result *json.RawMessage `json:"result,omitempty"`
|
||||||
|
Error *Error `json:"error,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run starts a read loop on the supplied reader.
|
||||||
|
// It must be called exactly once for each Conn.
|
||||||
|
// It returns only when the reader is closed or there is an error in the stream.
|
||||||
|
func (c *Conn) run(ctx context.Context) error {
|
||||||
|
ctx, cancelRun := context.WithCancel(ctx)
|
||||||
|
for {
|
||||||
|
// get the data for a message
|
||||||
|
data, err := c.stream.Read(ctx)
|
||||||
|
if err != nil {
|
||||||
|
// the stream failed, we cannot continue
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// read a combined message
|
||||||
|
msg := &combined{}
|
||||||
|
if err := json.Unmarshal(data, msg); err != nil {
|
||||||
|
// a badly formed message arrived, log it and continue
|
||||||
|
// we trust the stream to have isolated the error to just this message
|
||||||
|
c.log("read", nil, "", nil, NewErrorf(0, "unmarshal failed: %v", err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// work out which kind of message we have
|
||||||
|
switch {
|
||||||
|
case msg.Method != "":
|
||||||
|
// if method is set it must be a request
|
||||||
|
request := &Request{
|
||||||
|
Method: msg.Method,
|
||||||
|
Params: msg.Params,
|
||||||
|
ID: msg.ID,
|
||||||
|
}
|
||||||
|
if request.IsNotify() {
|
||||||
|
c.log("notify =>", request.ID, request.Method, request.Params, nil)
|
||||||
|
// we have a Notify, forward to the handler in a go routine
|
||||||
|
go func() {
|
||||||
|
if _, err := c.handle(ctx, c, request); err != nil {
|
||||||
|
// notify produced an error, we can't forward it to the other side
|
||||||
|
// because there is no id, so we just log it
|
||||||
|
c.log("notify failed", nil, request.Method, nil, err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
} else {
|
||||||
|
// we have a Call, forward to the handler in another go routine
|
||||||
|
reqCtx, cancelReq := context.WithCancel(ctx)
|
||||||
|
c.handlingMu.Lock()
|
||||||
|
c.handling[*request.ID] = cancelReq
|
||||||
|
c.handlingMu.Unlock()
|
||||||
|
go func() {
|
||||||
|
defer func() {
|
||||||
|
// clean up the cancel handler on the way out
|
||||||
|
c.handlingMu.Lock()
|
||||||
|
delete(c.handling, *request.ID)
|
||||||
|
c.handlingMu.Unlock()
|
||||||
|
cancelReq()
|
||||||
|
}()
|
||||||
|
c.log("call =>", request.ID, request.Method, request.Params, nil)
|
||||||
|
resp, callErr := c.handle(reqCtx, c, request)
|
||||||
|
var result *json.RawMessage
|
||||||
|
if result, err = marshalToRaw(resp); err != nil {
|
||||||
|
callErr = &Error{Message: err.Error()}
|
||||||
|
}
|
||||||
|
response := &Response{
|
||||||
|
Result: result,
|
||||||
|
Error: callErr,
|
||||||
|
ID: request.ID,
|
||||||
|
}
|
||||||
|
data, err := json.Marshal(response)
|
||||||
|
if err != nil {
|
||||||
|
// failure to marshal leaves the call without a response
|
||||||
|
// possibly we could attempt to respond with a different message
|
||||||
|
// but we can probably rely on timeouts instead
|
||||||
|
c.log("respond =!>", request.ID, request.Method, nil, NewErrorf(0, "%s", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.log("respond =>", response.ID, "", response.Result, response.Error)
|
||||||
|
if err = c.stream.Write(ctx, data); err != nil {
|
||||||
|
// if a stream write fails, we really need to shut down the whole
|
||||||
|
// stream and return from the run
|
||||||
|
c.log("respond =!>", nil, request.Method, nil, NewErrorf(0, "%s", err))
|
||||||
|
cancelRun()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
case msg.ID != nil:
|
||||||
|
// we have a response, get the pending entry from the map
|
||||||
|
c.pendingMu.Lock()
|
||||||
|
rchan := c.pending[*msg.ID]
|
||||||
|
if rchan != nil {
|
||||||
|
delete(c.pending, *msg.ID)
|
||||||
|
}
|
||||||
|
c.pendingMu.Unlock()
|
||||||
|
// and send the reply to the channel
|
||||||
|
response := &Response{
|
||||||
|
Result: msg.Result,
|
||||||
|
Error: msg.Error,
|
||||||
|
ID: msg.ID,
|
||||||
|
}
|
||||||
|
c.log("response =>", response.ID, "", response.Result, response.Error)
|
||||||
|
rchan <- response
|
||||||
|
close(rchan)
|
||||||
|
default:
|
||||||
|
c.log("invalid =>", nil, "", nil, NewErrorf(0, "message not a call, notify or response, ignoring"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func marshalToRaw(obj interface{}) (*json.RawMessage, error) {
|
||||||
|
data, err := json.Marshal(obj)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
raw := json.RawMessage(data)
|
||||||
|
return &raw, nil
|
||||||
|
}
|
159
internal/jsonrpc2/jsonrpc2_test.go
Normal file
159
internal/jsonrpc2/jsonrpc2_test.go
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
// Copyright 2018 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.
|
||||||
|
|
||||||
|
package jsonrpc2_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"path"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"golang.org/x/tools/internal/jsonrpc2"
|
||||||
|
)
|
||||||
|
|
||||||
|
var logRPC = flag.Bool("logrpc", false, "Enable jsonrpc2 communication logging")
|
||||||
|
|
||||||
|
type callTest struct {
|
||||||
|
method string
|
||||||
|
params interface{}
|
||||||
|
expect interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var callTests = []callTest{
|
||||||
|
{"no_args", nil, true},
|
||||||
|
{"one_string", "fish", "got:fish"},
|
||||||
|
{"one_number", 10, "got:10"},
|
||||||
|
{"join", []string{"a", "b", "c"}, "a/b/c"},
|
||||||
|
//TODO: expand the test cases
|
||||||
|
}
|
||||||
|
|
||||||
|
func (test *callTest) newResults() interface{} {
|
||||||
|
switch e := test.expect.(type) {
|
||||||
|
case []interface{}:
|
||||||
|
var r []interface{}
|
||||||
|
for _, v := range e {
|
||||||
|
r = append(r, reflect.New(reflect.TypeOf(v)).Interface())
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
case nil:
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return reflect.New(reflect.TypeOf(test.expect)).Interface()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (test *callTest) verifyResults(t *testing.T, results interface{}) {
|
||||||
|
if results == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val := reflect.Indirect(reflect.ValueOf(results)).Interface()
|
||||||
|
if !reflect.DeepEqual(val, test.expect) {
|
||||||
|
t.Errorf("%v:Results are incorrect, got %+v expect %+v", test.method, val, test.expect)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPlainCall(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
a, b := prepare(ctx, t, false)
|
||||||
|
for _, test := range callTests {
|
||||||
|
results := test.newResults()
|
||||||
|
if err := a.Call(ctx, test.method, test.params, results); err != nil {
|
||||||
|
t.Fatalf("%v:Call failed: %v", test.method, err)
|
||||||
|
}
|
||||||
|
test.verifyResults(t, results)
|
||||||
|
if err := b.Call(ctx, test.method, test.params, results); err != nil {
|
||||||
|
t.Fatalf("%v:Call failed: %v", test.method, err)
|
||||||
|
}
|
||||||
|
test.verifyResults(t, results)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHeaderCall(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
a, b := prepare(ctx, t, true)
|
||||||
|
for _, test := range callTests {
|
||||||
|
results := test.newResults()
|
||||||
|
if err := a.Call(ctx, test.method, test.params, results); err != nil {
|
||||||
|
t.Fatalf("%v:Call failed: %v", test.method, err)
|
||||||
|
}
|
||||||
|
test.verifyResults(t, results)
|
||||||
|
if err := b.Call(ctx, test.method, test.params, results); err != nil {
|
||||||
|
t.Fatalf("%v:Call failed: %v", test.method, err)
|
||||||
|
}
|
||||||
|
test.verifyResults(t, results)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func prepare(ctx context.Context, t *testing.T, withHeaders bool) (*testHandler, *testHandler) {
|
||||||
|
a := &testHandler{t: t}
|
||||||
|
b := &testHandler{t: t}
|
||||||
|
a.reader, b.writer = io.Pipe()
|
||||||
|
b.reader, a.writer = io.Pipe()
|
||||||
|
for _, h := range []*testHandler{a, b} {
|
||||||
|
h := h
|
||||||
|
if withHeaders {
|
||||||
|
h.stream = jsonrpc2.NewHeaderStream(h.reader, h.writer)
|
||||||
|
} else {
|
||||||
|
h.stream = jsonrpc2.NewStream(h.reader, h.writer)
|
||||||
|
}
|
||||||
|
args := []interface{}{handle}
|
||||||
|
if *logRPC {
|
||||||
|
args = append(args, jsonrpc2.Log)
|
||||||
|
}
|
||||||
|
h.Conn = jsonrpc2.NewConn(ctx, h.stream, args...)
|
||||||
|
go func() {
|
||||||
|
defer func() {
|
||||||
|
h.reader.Close()
|
||||||
|
h.writer.Close()
|
||||||
|
}()
|
||||||
|
if err := h.Conn.Wait(ctx); err != nil {
|
||||||
|
t.Fatalf("Stream failed: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
return a, b
|
||||||
|
}
|
||||||
|
|
||||||
|
type testHandler struct {
|
||||||
|
t *testing.T
|
||||||
|
reader *io.PipeReader
|
||||||
|
writer *io.PipeWriter
|
||||||
|
stream jsonrpc2.Stream
|
||||||
|
*jsonrpc2.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
func handle(ctx context.Context, c *jsonrpc2.Conn, r *jsonrpc2.Request) (interface{}, *jsonrpc2.Error) {
|
||||||
|
switch r.Method {
|
||||||
|
case "no_args":
|
||||||
|
if r.Params != nil {
|
||||||
|
return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeInvalidParams, "Expected no params")
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
case "one_string":
|
||||||
|
var v string
|
||||||
|
if err := json.Unmarshal(*r.Params, &v); err != nil {
|
||||||
|
return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err.Error())
|
||||||
|
}
|
||||||
|
return "got:" + v, nil
|
||||||
|
case "one_number":
|
||||||
|
var v int
|
||||||
|
if err := json.Unmarshal(*r.Params, &v); err != nil {
|
||||||
|
return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err.Error())
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("got:%d", v), nil
|
||||||
|
case "join":
|
||||||
|
var v []string
|
||||||
|
if err := json.Unmarshal(*r.Params, &v); err != nil {
|
||||||
|
return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err.Error())
|
||||||
|
}
|
||||||
|
return path.Join(v...), nil
|
||||||
|
default:
|
||||||
|
return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeMethodNotFound, "method %q not found", r.Method)
|
||||||
|
}
|
||||||
|
}
|
34
internal/jsonrpc2/log.go
Normal file
34
internal/jsonrpc2/log.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
// Copyright 2018 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.
|
||||||
|
|
||||||
|
package jsonrpc2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Log is an implementation of Logger that outputs using log.Print
|
||||||
|
// It is not used by default, but is provided for easy logging in users code.
|
||||||
|
func Log(mode string, id *ID, method string, payload *json.RawMessage, err *Error) {
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
fmt.Fprint(buf, mode)
|
||||||
|
if id == nil {
|
||||||
|
fmt.Fprintf(buf, " []")
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(buf, " [%v]", id)
|
||||||
|
}
|
||||||
|
if method != "" {
|
||||||
|
fmt.Fprintf(buf, " %s", method)
|
||||||
|
}
|
||||||
|
if payload != nil {
|
||||||
|
fmt.Fprintf(buf, " %s", *payload)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(buf, " failed: %s", err)
|
||||||
|
}
|
||||||
|
log.Print(buf)
|
||||||
|
}
|
146
internal/jsonrpc2/stream.go
Normal file
146
internal/jsonrpc2/stream.go
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
// Copyright 2018 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.
|
||||||
|
|
||||||
|
package jsonrpc2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Stream abstracts the transport mechanics from the JSON RPC protocol.
|
||||||
|
// A Conn reads and writes messages using the stream it was provided on
|
||||||
|
// construction, and assumes that each call to Read or Write fully transfers
|
||||||
|
// a single message, or returns an error.
|
||||||
|
type Stream interface {
|
||||||
|
// Read gets the next message from the stream.
|
||||||
|
// It is never called concurrently.
|
||||||
|
Read(context.Context) ([]byte, error)
|
||||||
|
// Write sends a message to the stream.
|
||||||
|
// It must be safe for concurrent use.
|
||||||
|
Write(context.Context, []byte) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStream returns a Stream built on top of an io.Reader and io.Writer
|
||||||
|
// The messages are sent with no wrapping, and rely on json decode consistency
|
||||||
|
// to determine message boundaries.
|
||||||
|
func NewStream(in io.Reader, out io.Writer) Stream {
|
||||||
|
return &plainStream{
|
||||||
|
in: json.NewDecoder(in),
|
||||||
|
out: out,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type plainStream struct {
|
||||||
|
in *json.Decoder
|
||||||
|
outMu sync.Mutex
|
||||||
|
out io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *plainStream) Read(ctx context.Context) ([]byte, error) {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, ctx.Err()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
var raw json.RawMessage
|
||||||
|
if err := s.in.Decode(&raw); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return raw, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *plainStream) Write(ctx context.Context, data []byte) error {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
s.outMu.Lock()
|
||||||
|
_, err := s.out.Write(data)
|
||||||
|
s.outMu.Unlock()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHeaderStream returns a Stream built on top of an io.Reader and io.Writer
|
||||||
|
// The messages are sent with HTTP content length and MIME type headers.
|
||||||
|
// This is the format used by LSP and others.
|
||||||
|
func NewHeaderStream(in io.Reader, out io.Writer) Stream {
|
||||||
|
return &headerStream{
|
||||||
|
in: bufio.NewReader(in),
|
||||||
|
out: out,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type headerStream struct {
|
||||||
|
in *bufio.Reader
|
||||||
|
outMu sync.Mutex
|
||||||
|
out io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *headerStream) Read(ctx context.Context) ([]byte, error) {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, ctx.Err()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
var length int64
|
||||||
|
// read the header, stop on the first empty line
|
||||||
|
for {
|
||||||
|
line, err := s.in.ReadString('\n')
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed reading header line %q", err)
|
||||||
|
}
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
|
// check we have a header line
|
||||||
|
if line == "" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
colon := strings.IndexRune(line, ':')
|
||||||
|
if colon < 0 {
|
||||||
|
return nil, fmt.Errorf("invalid header line %q", line)
|
||||||
|
}
|
||||||
|
name, value := line[:colon], strings.TrimSpace(line[colon+1:])
|
||||||
|
switch name {
|
||||||
|
case "Content-Length":
|
||||||
|
if length, err = strconv.ParseInt(value, 10, 32); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed parsing Content-Length: %v", value)
|
||||||
|
}
|
||||||
|
if length <= 0 {
|
||||||
|
return nil, fmt.Errorf("invalid Content-Length: %v", length)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// ignoring unknown headers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if length == 0 {
|
||||||
|
return nil, fmt.Errorf("missing Content-Length header")
|
||||||
|
}
|
||||||
|
data := make([]byte, length)
|
||||||
|
if _, err := io.ReadFull(s.in, data); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *headerStream) Write(ctx context.Context, data []byte) error {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
s.outMu.Lock()
|
||||||
|
_, err := fmt.Fprintf(s.out, "Content-Length: %v\r\n\r\n", len(data))
|
||||||
|
if err == nil {
|
||||||
|
_, err = s.out.Write(data)
|
||||||
|
}
|
||||||
|
s.outMu.Unlock()
|
||||||
|
return err
|
||||||
|
}
|
136
internal/jsonrpc2/wire.go
Normal file
136
internal/jsonrpc2/wire.go
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
// Copyright 2018 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.
|
||||||
|
|
||||||
|
package jsonrpc2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// this file contains the go forms of the wire specification
|
||||||
|
// see http://www.jsonrpc.org/specification for details
|
||||||
|
|
||||||
|
const (
|
||||||
|
// CodeUnknownError should be used for all non coded errors.
|
||||||
|
CodeUnknownError = -32001
|
||||||
|
// CodeParseError is used when invalid JSON was received by the server.
|
||||||
|
CodeParseError = -32700
|
||||||
|
//CodeInvalidRequest is used when the JSON sent is not a valid Request object.
|
||||||
|
CodeInvalidRequest = -32600
|
||||||
|
// CodeMethodNotFound should be returned by the handler when the method does
|
||||||
|
// not exist / is not available.
|
||||||
|
CodeMethodNotFound = -32601
|
||||||
|
// CodeInvalidParams should be returned by the handler when method
|
||||||
|
// parameter(s) were invalid.
|
||||||
|
CodeInvalidParams = -32602
|
||||||
|
// CodeInternalError is not currently returned but defined for completeness.
|
||||||
|
CodeInternalError = -32603
|
||||||
|
)
|
||||||
|
|
||||||
|
// Request is sent to a server to represent a Call or Notify operaton.
|
||||||
|
type Request struct {
|
||||||
|
// VersionTag is always encoded as the string "2.0"
|
||||||
|
VersionTag VersionTag `json:"jsonrpc"`
|
||||||
|
// Method is a string containing the method name to invoke.
|
||||||
|
Method string `json:"method"`
|
||||||
|
// Params is either a struct or an array with the parameters of the method.
|
||||||
|
Params *json.RawMessage `json:"params,omitempty"`
|
||||||
|
// The id of this request, used to tie the Response back to the request.
|
||||||
|
// Will be either a string or a number. If not set, the Request is a notify,
|
||||||
|
// and no response is possible.
|
||||||
|
ID *ID `json:"id,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response is a reply to a Request.
|
||||||
|
// It will always have the ID field set to tie it back to a request, and will
|
||||||
|
// have either the Result or Error fields set depending on whether it is a
|
||||||
|
// success or failure response.
|
||||||
|
type Response struct {
|
||||||
|
// VersionTag is always encoded as the string "2.0"
|
||||||
|
VersionTag VersionTag `json:"jsonrpc"`
|
||||||
|
// Result is the response value, and is required on success.
|
||||||
|
Result *json.RawMessage `json:"result,omitempty"`
|
||||||
|
// Error is a structured error response if the call fails.
|
||||||
|
Error *Error `json:"error,omitempty"`
|
||||||
|
// ID must be set and is the identifier of the Request this is a response to.
|
||||||
|
ID *ID `json:"id,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error represents a structured error in a Response.
|
||||||
|
type Error struct {
|
||||||
|
// Code is an error code indicating the type of failure.
|
||||||
|
Code int64 `json:"code"`
|
||||||
|
// Message is a short description of the error.
|
||||||
|
Message string `json:"message"`
|
||||||
|
// Data is optional structured data containing additional information about the error.
|
||||||
|
Data *json.RawMessage `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// VersionTag is a special 0 sized struct that encodes as the jsonrpc version
|
||||||
|
// tag.
|
||||||
|
// It will fail during decode if it is not the correct version tag in the
|
||||||
|
// stream.
|
||||||
|
type VersionTag struct{}
|
||||||
|
|
||||||
|
// ID is a Request identifier.
|
||||||
|
// Only one of either the Name or Number members will be set, using the
|
||||||
|
// number form if the Name is the empty string.
|
||||||
|
type ID struct {
|
||||||
|
Name string
|
||||||
|
Number int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNotify returns true if this request is a notification.
|
||||||
|
func (r *Request) IsNotify() bool {
|
||||||
|
return r.ID == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err *Error) Error() string {
|
||||||
|
if err == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return err.Message
|
||||||
|
}
|
||||||
|
|
||||||
|
func (VersionTag) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal("2.0")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (VersionTag) UnmarshalJSON(data []byte) error {
|
||||||
|
version := ""
|
||||||
|
if err := json.Unmarshal(data, &version); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if version != "2.0" {
|
||||||
|
return fmt.Errorf("Invalid RPC version %v", version)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a string representation of the ID.
|
||||||
|
// The representation is non ambiguous, string forms are quoted, number forms
|
||||||
|
// are preceded by a #
|
||||||
|
func (id *ID) String() string {
|
||||||
|
if id.Name != "" {
|
||||||
|
return strconv.Quote(id.Name)
|
||||||
|
}
|
||||||
|
return "#" + strconv.FormatInt(id.Number, 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (id *ID) MarshalJSON() ([]byte, error) {
|
||||||
|
if id.Name != "" {
|
||||||
|
return json.Marshal(id.Name)
|
||||||
|
}
|
||||||
|
return json.Marshal(id.Number)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (id *ID) UnmarshalJSON(data []byte) error {
|
||||||
|
*id = ID{}
|
||||||
|
if err := json.Unmarshal(data, &id.Number); err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return json.Unmarshal(data, &id.Name)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user