pax_global_header00006660000000000000000000000064143753540300014516gustar00rootroot0000000000000052 comment=ae88a5e7c0fede2c609c9ac56162c73e688f8b3d jsonrpc2-0.2.0/000077500000000000000000000000001437535403000132555ustar00rootroot00000000000000jsonrpc2-0.2.0/.github/000077500000000000000000000000001437535403000146155ustar00rootroot00000000000000jsonrpc2-0.2.0/.github/workflows/000077500000000000000000000000001437535403000166525ustar00rootroot00000000000000jsonrpc2-0.2.0/.github/workflows/ci.yml000066400000000000000000000012371437535403000177730ustar00rootroot00000000000000name: CI on: pull_request: {} push: branches: - master jobs: test: strategy: fail-fast: false matrix: go: - 1.16 name: Go ${{ matrix.go }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up Go uses: actions/setup-go@v2 with: go-version: ${{ matrix.go }} id: go - name: Get dependencies run: go get -t -v ./... - name: Install staticcheck run: go install honnef.co/go/tools/cmd/staticcheck@v0.2.2 - name: Lint run: staticcheck -checks=all ./... - name: Test run: go test -v -race ./... jsonrpc2-0.2.0/.github/workflows/lsif.yml000066400000000000000000000004601437535403000203320ustar00rootroot00000000000000name: LSIF on: - push jobs: lsif-go: runs-on: ubuntu-latest container: sourcegraph/lsif-go steps: - uses: actions/checkout@v1 - name: Generate LSIF data run: lsif-go - name: Upload LSIF data run: src lsif upload -github-token=${{ secrets.GITHUB_TOKEN }} jsonrpc2-0.2.0/.travis.yml000066400000000000000000000000741437535403000153670ustar00rootroot00000000000000language: go go: - 1.x script: - go test -race -v ./... jsonrpc2-0.2.0/LICENSE000066400000000000000000000020601437535403000142600ustar00rootroot00000000000000Copyright (c) 2016 Sourcegraph Inc MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. jsonrpc2-0.2.0/README.md000066400000000000000000000014121437535403000145320ustar00rootroot00000000000000# jsonrpc2: JSON-RPC 2.0 implementation for Go [![Build Status](https://travis-ci.org/sourcegraph/jsonrpc2.svg)](https://travis-ci.org/sourcegraph/jsonrpc2) [![Sourcegraph](https://sourcegraph.com/github.com/sourcegraph/jsonrpc2/-/badge.svg)](https://sourcegraph.com/github.com/sourcegraph/jsonrpc2?badge) [![GoDoc](https://godoc.org/github.com/sourcegraph/jsonrpc2?status.svg)](https://godoc.org/github.com/sourcegraph/jsonrpc2) Package jsonrpc2 provides a [Go](https://golang.org) implementation of [JSON-RPC 2.0](http://www.jsonrpc.org/specification). This package is **experimental** until further notice. [**Open the code in Sourcegraph**](https://sourcegraph.com/github.com/sourcegraph/jsonrpc2) ## Known issues * Batch requests and responses are not yet supported. jsonrpc2-0.2.0/async.go000066400000000000000000000005721437535403000147250ustar00rootroot00000000000000package jsonrpc2 import "context" // AsyncHandler wraps a Handler such that each request is handled in its own // goroutine. It is a convenience wrapper. func AsyncHandler(h Handler) Handler { return asyncHandler{h} } type asyncHandler struct { Handler } func (h asyncHandler) Handle(ctx context.Context, conn *Conn, req *Request) { go h.Handler.Handle(ctx, conn, req) } jsonrpc2-0.2.0/call_opt.go000066400000000000000000000026151437535403000154050ustar00rootroot00000000000000package jsonrpc2 // CallOption is an option that can be provided to (*Conn).Call to // configure custom behavior. See Meta. type CallOption interface { apply(r *Request) error } type callOptionFunc func(r *Request) error func (c callOptionFunc) apply(r *Request) error { return c(r) } // Meta returns a call option which attaches the given meta object to // the JSON-RPC 2.0 request (this is a Sourcegraph extension to JSON // RPC 2.0 for carrying metadata). func Meta(meta interface{}) CallOption { return callOptionFunc(func(r *Request) error { return r.SetMeta(meta) }) } // ExtraField returns a call option which attaches the given name/value pair to // the JSON-RPC 2.0 request. This can be used to add arbitrary extensions to // JSON RPC 2.0. func ExtraField(name string, value interface{}) CallOption { return callOptionFunc(func(r *Request) error { return r.SetExtraField(name, value) }) } // PickID returns a call option which sets the ID on a request. Care must be // taken to ensure there are no conflicts with any previously picked ID, nor // with the default sequence ID. func PickID(id ID) CallOption { return callOptionFunc(func(r *Request) error { r.ID = id return nil }) } // StringID returns a call option that instructs the request ID to be set as a // string. func StringID() CallOption { return callOptionFunc(func(r *Request) error { r.ID.IsString = true return nil }) } jsonrpc2-0.2.0/call_opt_test.go000066400000000000000000000074751437535403000164550ustar00rootroot00000000000000package jsonrpc2_test import ( "context" "fmt" "testing" "github.com/sourcegraph/jsonrpc2" ) func TestPickID(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() a, b := inMemoryPeerConns() defer a.Close() defer b.Close() handler := handlerFunc(func(ctx context.Context, conn *jsonrpc2.Conn, req *jsonrpc2.Request) { if err := conn.Reply(ctx, req.ID, fmt.Sprintf("hello, #%s: %s", req.ID, *req.Params)); err != nil { t.Error(err) } }) connA := jsonrpc2.NewConn(ctx, jsonrpc2.NewBufferedStream(a, jsonrpc2.VSCodeObjectCodec{}), handler) connB := jsonrpc2.NewConn(ctx, jsonrpc2.NewBufferedStream(b, jsonrpc2.VSCodeObjectCodec{}), noopHandler{}) defer connA.Close() defer connB.Close() const n = 100 for i := 0; i < n; i++ { var opts []jsonrpc2.CallOption id := jsonrpc2.ID{Num: uint64(i)} // This is the actual test, every 3rd request we specify the // ID and ensure we get a response with the correct ID echoed // back if i%3 == 0 { id = jsonrpc2.ID{ Str: fmt.Sprintf("helloworld-%d", i/3), IsString: true, } opts = append(opts, jsonrpc2.PickID(id)) } var got string if err := connB.Call(ctx, "f", []int32{1, 2, 3}, &got, opts...); err != nil { t.Fatal(err) } if want := fmt.Sprintf("hello, #%s: [1,2,3]", id); got != want { t.Errorf("got result %q, want %q", got, want) } } } func TestStringID(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() a, b := inMemoryPeerConns() defer a.Close() defer b.Close() handler := handlerFunc(func(ctx context.Context, conn *jsonrpc2.Conn, req *jsonrpc2.Request) { replyWithError := func(msg string) { respErr := &jsonrpc2.Error{Code: jsonrpc2.CodeInvalidRequest, Message: msg} if err := conn.ReplyWithError(ctx, req.ID, respErr); err != nil { t.Error(err) } } if !req.ID.IsString { replyWithError("ID.IsString should be true") return } if len(req.ID.Str) == 0 { replyWithError("ID.Str should be populated but is empty") return } if err := conn.Reply(ctx, req.ID, "ok"); err != nil { t.Error(err) } }) connA := jsonrpc2.NewConn(ctx, jsonrpc2.NewBufferedStream(a, jsonrpc2.VSCodeObjectCodec{}), handler) connB := jsonrpc2.NewConn(ctx, jsonrpc2.NewBufferedStream(b, jsonrpc2.VSCodeObjectCodec{}), noopHandler{}) defer connA.Close() defer connB.Close() var res string if err := connB.Call(ctx, "f", nil, &res, jsonrpc2.StringID()); err != nil { t.Fatal(err) } } func TestExtraField(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() a, b := inMemoryPeerConns() defer a.Close() defer b.Close() handler := handlerFunc(func(ctx context.Context, conn *jsonrpc2.Conn, req *jsonrpc2.Request) { replyWithError := func(msg string) { respErr := &jsonrpc2.Error{Code: jsonrpc2.CodeInvalidRequest, Message: msg} if err := conn.ReplyWithError(ctx, req.ID, respErr); err != nil { t.Error(err) } } var sessionID string for _, field := range req.ExtraFields { if field.Name != "sessionId" { continue } var ok bool sessionID, ok = field.Value.(string) if !ok { t.Errorf("\"sessionId\" is not a string: %v", field.Value) } } if sessionID == "" { replyWithError("sessionId must be set") return } if sessionID != "session" { replyWithError("sessionId has the wrong value") return } if err := conn.Reply(ctx, req.ID, "ok"); err != nil { t.Error(err) } }) connA := jsonrpc2.NewConn(ctx, jsonrpc2.NewBufferedStream(a, jsonrpc2.VSCodeObjectCodec{}), handler) connB := jsonrpc2.NewConn(ctx, jsonrpc2.NewBufferedStream(b, jsonrpc2.VSCodeObjectCodec{}), noopHandler{}) defer connA.Close() defer connB.Close() var res string if err := connB.Call(ctx, "f", nil, &res, jsonrpc2.ExtraField("sessionId", "session")); err != nil { t.Fatal(err) } } jsonrpc2-0.2.0/codec_test.go000066400000000000000000000037541437535403000157310ustar00rootroot00000000000000package jsonrpc2_test import ( "bufio" "bytes" "context" "encoding/json" "net" "reflect" "strings" "testing" "github.com/sourcegraph/jsonrpc2" ) func TestVarintObjectCodec(t *testing.T) { want := 789 var buf bytes.Buffer if err := (jsonrpc2.VarintObjectCodec{}).WriteObject(&buf, want); err != nil { t.Fatal(err) } var v int if err := (jsonrpc2.VarintObjectCodec{}).ReadObject(bufio.NewReader(&buf), &v); err != nil { t.Fatal(err) } if want := want; v != want { t.Errorf("got %v, want %v", v, want) } } func TestVSCodeObjectCodec_ReadObject(t *testing.T) { s := "Content-Type: foo\r\nContent-Length: 123\r\n\r\n789" var v int if err := (jsonrpc2.VSCodeObjectCodec{}).ReadObject(bufio.NewReader(strings.NewReader(s)), &v); err != nil { t.Fatal(err) } if want := 789; v != want { t.Errorf("got %v, want %v", v, want) } } func TestPlainObjectCodec(t *testing.T) { type Message struct { One string Two uint Three bool } cA, cB := net.Pipe() connA := jsonrpc2.NewConn( context.Background(), jsonrpc2.NewBufferedStream(cA, jsonrpc2.PlainObjectCodec{}), noopHandler{}, ) defer connA.Close() // echoHandler unmarshals the request's params object and echos the object // back as the response's result. var echoHandler handlerFunc = func(ctx context.Context, conn *jsonrpc2.Conn, req *jsonrpc2.Request) { msg := &Message{} if err := json.Unmarshal(*req.Params, msg); err != nil { conn.ReplyWithError(ctx, req.ID, &jsonrpc2.Error{Code: jsonrpc2.CodeInvalidRequest, Message: err.Error()}) return } conn.Reply(ctx, req.ID, msg) } connB := jsonrpc2.NewConn( context.Background(), jsonrpc2.NewBufferedStream(cB, jsonrpc2.PlainObjectCodec{}), echoHandler, ) defer connB.Close() req := &Message{One: "hello world", Two: 123, Three: true} res := &Message{} if err := connA.Call(context.Background(), "f", req, res); err != nil { t.Fatal(err) } if got, want := res, req; !reflect.DeepEqual(got, want) { t.Fatalf("got %v, want %v", res, req) } } jsonrpc2-0.2.0/conn.go000066400000000000000000000263211437535403000145450ustar00rootroot00000000000000package jsonrpc2 import ( "bytes" "context" "encoding/json" "errors" "io" "log" "os" "strconv" "sync" ) // Conn is a JSON-RPC client/server connection. The JSON-RPC protocol // is symmetric, so a Conn runs on both ends of a client-server // connection. type Conn struct { stream ObjectStream h Handler mu sync.Mutex closed bool seq uint64 pending map[ID]*call sending sync.Mutex disconnect chan struct{} logger Logger // Set by ConnOpt funcs. onRecv []func(*Request, *Response) onSend []func(*Request, *Response) } var _ JSONRPC2 = (*Conn)(nil) // NewConn creates a new JSON-RPC client/server connection using the // given ReadWriteCloser (typically a TCP connection or stdio). The // JSON-RPC protocol is symmetric, so a Conn runs on both ends of a // client-server connection. // // NewClient consumes conn, so you should call Close on the returned // client not on the given conn. func NewConn(ctx context.Context, stream ObjectStream, h Handler, opts ...ConnOpt) *Conn { c := &Conn{ stream: stream, h: h, pending: map[ID]*call{}, disconnect: make(chan struct{}), logger: log.New(os.Stderr, "", log.LstdFlags), } for _, opt := range opts { if opt == nil { continue } opt(c) } go c.readMessages(ctx) return c } // Close closes the JSON-RPC connection. The connection may not be // used after it has been closed. func (c *Conn) Close() error { return c.close(nil) } // Call initiates a JSON-RPC call using the specified method and params, and // waits for the response. If the response is successful, its result is stored // in result (a pointer to a value that can be JSON-unmarshaled into); // otherwise, a non-nil error is returned. See DispatchCall for more details. func (c *Conn) Call(ctx context.Context, method string, params, result interface{}, opts ...CallOption) error { call, err := c.DispatchCall(ctx, method, params, opts...) if err != nil { return err } return call.Wait(ctx, result) } // DisconnectNotify returns a channel that is closed when the // underlying connection is disconnected. func (c *Conn) DisconnectNotify() <-chan struct{} { return c.disconnect } // DispatchCall dispatches a JSON-RPC call using the specified method and // params, and returns a call proxy or an error. Call Wait() on the returned // proxy to receive the response. Only use this function if you need to do work // after dispatching the request, otherwise use Call. // // The params member is omitted from the JSON-RPC request if the given params is // nil. Use json.RawMessage("null") to send a JSON-RPC request with its params // member set to null. func (c *Conn) DispatchCall(ctx context.Context, method string, params interface{}, opts ...CallOption) (Waiter, error) { req := &Request{Method: method} for _, opt := range opts { if opt == nil { continue } if err := opt.apply(req); err != nil { return Waiter{}, err } } if params != nil { if err := req.SetParams(params); err != nil { return Waiter{}, err } } call, err := c.send(ctx, &anyMessage{request: req}, true) if err != nil { return Waiter{}, err } return Waiter{call: call}, nil } // Notify is like Call, but it returns when the notification request is sent // (without waiting for a response, because JSON-RPC notifications do not have // responses). // // The params member is omitted from the JSON-RPC request if the given params is // nil. Use json.RawMessage("null") to send a JSON-RPC request with its params // member set to null. func (c *Conn) Notify(ctx context.Context, method string, params interface{}, opts ...CallOption) error { req := &Request{Method: method, Notif: true} for _, opt := range opts { if opt == nil { continue } if err := opt.apply(req); err != nil { return err } } if params != nil { if err := req.SetParams(params); err != nil { return err } } _, err := c.send(ctx, &anyMessage{request: req}, false) return err } // Reply sends a successful response with a result. func (c *Conn) Reply(ctx context.Context, id ID, result interface{}) error { resp := &Response{ID: id} if err := resp.SetResult(result); err != nil { return err } _, err := c.send(ctx, &anyMessage{response: resp}, false) return err } // ReplyWithError sends a response with an error. func (c *Conn) ReplyWithError(ctx context.Context, id ID, respErr *Error) error { _, err := c.send(ctx, &anyMessage{response: &Response{ID: id, Error: respErr}}, false) return err } // SendResponse sends resp to the peer. It is lower level than (*Conn).Reply. func (c *Conn) SendResponse(ctx context.Context, resp *Response) error { _, err := c.send(ctx, &anyMessage{response: resp}, false) return err } func (c *Conn) close(cause error) error { c.sending.Lock() c.mu.Lock() defer c.sending.Unlock() defer c.mu.Unlock() if c.closed { return ErrClosed } for _, call := range c.pending { close(call.done) } if cause != nil && cause != io.EOF && cause != io.ErrUnexpectedEOF { c.logger.Printf("jsonrpc2: protocol error: %v\n", cause) } close(c.disconnect) c.closed = true return c.stream.Close() } func (c *Conn) readMessages(ctx context.Context) { var err error for err == nil { var m anyMessage err = c.stream.ReadObject(&m) if err != nil { break } switch { case m.request != nil: for _, onRecv := range c.onRecv { onRecv(m.request, nil) } c.h.Handle(ctx, c, m.request) case m.response != nil: resp := m.response if resp != nil { id := resp.ID c.mu.Lock() call := c.pending[id] delete(c.pending, id) c.mu.Unlock() if call != nil { call.response = resp } if len(c.onRecv) > 0 { var req *Request if call != nil { req = call.request } for _, onRecv := range c.onRecv { onRecv(req, resp) } } switch { case call == nil: c.logger.Printf("jsonrpc2: ignoring response #%s with no corresponding request\n", id) case resp.Error != nil: call.done <- resp.Error close(call.done) default: call.done <- nil close(call.done) } } } } c.close(err) } func (c *Conn) send(_ context.Context, m *anyMessage, wait bool) (cc *call, err error) { c.sending.Lock() defer c.sending.Unlock() // m.request.ID could be changed, so we store a copy to correctly // clean up pending var id ID c.mu.Lock() if c.closed { c.mu.Unlock() return nil, ErrClosed } // Assign a default id if not set if m.request != nil && wait { cc = &call{request: m.request, seq: c.seq, done: make(chan error, 1)} isIDUnset := len(m.request.ID.Str) == 0 && m.request.ID.Num == 0 if isIDUnset { if m.request.ID.IsString { m.request.ID.Str = strconv.FormatUint(c.seq, 10) } else { m.request.ID.Num = c.seq } } c.seq++ } c.mu.Unlock() if len(c.onSend) > 0 { var ( req *Request resp *Response ) switch { case m.request != nil: req = m.request case m.response != nil: resp = m.response } for _, onSend := range c.onSend { onSend(req, resp) } } // Store requests so we can later associate them with incoming // responses. if m.request != nil && wait { c.mu.Lock() id = m.request.ID c.pending[id] = cc c.mu.Unlock() } // From here on, if we fail to send this, then we need to remove // this from the pending map so we don't block on it or pile up // pending entries for unsent messages. defer func() { if err != nil { if cc != nil { c.mu.Lock() delete(c.pending, id) c.mu.Unlock() } } }() if err := c.stream.WriteObject(m); err != nil { return nil, err } return cc, nil } // Waiter proxies an ongoing JSON-RPC call. type Waiter struct { *call } // Wait for the result of an ongoing JSON-RPC call. If the response // is successful, its result is stored in result (a pointer to a // value that can be JSON-unmarshaled into); otherwise, a non-nil // error is returned. func (w Waiter) Wait(ctx context.Context, result interface{}) error { select { case err, ok := <-w.call.done: if !ok { err = ErrClosed } if err != nil { return err } if result != nil { if w.call.response.Result == nil { w.call.response.Result = &jsonNull } if err := json.Unmarshal(*w.call.response.Result, result); err != nil { return err } } return nil case <-ctx.Done(): return ctx.Err() } } // call represents a JSON-RPC call over its entire lifecycle. type call struct { request *Request response *Response seq uint64 // the seq of the request done chan error } // anyMessage represents either a JSON Request or Response. type anyMessage struct { request *Request response *Response } func (m anyMessage) MarshalJSON() ([]byte, error) { var v interface{} switch { case m.request != nil && m.response == nil: v = m.request case m.request == nil && m.response != nil: v = m.response } if v != nil { return json.Marshal(v) } return nil, errors.New("jsonrpc2: message must have exactly one of the request or response fields set") } func (m *anyMessage) UnmarshalJSON(data []byte) error { // The presence of these fields distinguishes between the 2 // message types. type msg struct { ID interface{} `json:"id"` Method *string `json:"method"` Result anyValueWithExplicitNull `json:"result"` Error interface{} `json:"error"` } var isRequest, isResponse bool checkType := func(m *msg) error { mIsRequest := m.Method != nil mIsResponse := m.Result.null || m.Result.value != nil || m.Error != nil if (!mIsRequest && !mIsResponse) || (mIsRequest && mIsResponse) { return errors.New("jsonrpc2: unable to determine message type (request or response)") } if (mIsRequest && isResponse) || (mIsResponse && isRequest) { return errors.New("jsonrpc2: batch message type mismatch (must be all requests or all responses)") } isRequest = mIsRequest isResponse = mIsResponse return nil } if isArray := len(data) > 0 && data[0] == '['; isArray { var msgs []msg if err := json.Unmarshal(data, &msgs); err != nil { return err } if len(msgs) == 0 { return errors.New("jsonrpc2: invalid empty batch") } for i := range msgs { if err := checkType(&msg{ ID: msgs[i].ID, Method: msgs[i].Method, Result: msgs[i].Result, Error: msgs[i].Error, }); err != nil { return err } } } else { var m msg if err := json.Unmarshal(data, &m); err != nil { return err } if err := checkType(&m); err != nil { return err } } var v interface{} switch { case isRequest && !isResponse: v = &m.request case !isRequest && isResponse: v = &m.response } if err := json.Unmarshal(data, v); err != nil { return err } if !isRequest && isResponse && m.response.Error == nil && m.response.Result == nil { m.response.Result = &jsonNull } return nil } // anyValueWithExplicitNull is used to distinguish {} from // {"result":null} by anyMessage's JSON unmarshaler. type anyValueWithExplicitNull struct { null bool // JSON "null" value interface{} } func (v anyValueWithExplicitNull) MarshalJSON() ([]byte, error) { return json.Marshal(v.value) } func (v *anyValueWithExplicitNull) UnmarshalJSON(data []byte) error { data = bytes.TrimSpace(data) if string(data) == "null" { *v = anyValueWithExplicitNull{null: true} return nil } *v = anyValueWithExplicitNull{} return json.Unmarshal(data, &v.value) } jsonrpc2-0.2.0/conn_opt.go000066400000000000000000000057251437535403000154340ustar00rootroot00000000000000package jsonrpc2 import ( "encoding/json" "sync" ) // Logger interface implements one method - Printf. // You can use the stdlib logger *log.Logger type Logger interface { Printf(format string, v ...interface{}) } // ConnOpt is the type of function that can be passed to NewConn to // customize the Conn before it is created. type ConnOpt func(*Conn) // OnRecv causes all requests received on conn to invoke f(req, nil) // and all responses to invoke f(req, resp), func OnRecv(f func(*Request, *Response)) ConnOpt { return func(c *Conn) { c.onRecv = append(c.onRecv, f) } } // OnSend causes all requests sent on conn to invoke f(req, nil) and // all responses to invoke f(nil, resp), func OnSend(f func(*Request, *Response)) ConnOpt { return func(c *Conn) { c.onSend = append(c.onSend, f) } } // LogMessages causes all messages sent and received on conn to be // logged using the provided logger. func LogMessages(logger Logger) ConnOpt { return func(c *Conn) { // Remember reqs we have received so we can helpfully show the // request method in OnSend for responses. var ( mu sync.Mutex reqMethods = map[ID]string{} ) // Set custom logger from provided input c.logger = logger OnRecv(func(req *Request, resp *Response) { switch { case req != nil: mu.Lock() reqMethods[req.ID] = req.Method mu.Unlock() params, _ := json.Marshal(req.Params) if req.Notif { logger.Printf("jsonrpc2: --> notif: %s: %s\n", req.Method, params) } else { logger.Printf("jsonrpc2: --> request #%s: %s: %s\n", req.ID, req.Method, params) } case resp != nil: var method string if req != nil { method = req.Method } else { method = "(no matching request)" } switch { case resp.Result != nil: result, _ := json.Marshal(resp.Result) logger.Printf("jsonrpc2: --> result #%s: %s: %s\n", resp.ID, method, result) case resp.Error != nil: err, _ := json.Marshal(resp.Error) logger.Printf("jsonrpc2: --> error #%s: %s: %s\n", resp.ID, method, err) } } })(c) OnSend(func(req *Request, resp *Response) { switch { case req != nil: params, _ := json.Marshal(req.Params) if req.Notif { logger.Printf("jsonrpc2: <-- notif: %s: %s\n", req.Method, params) } else { logger.Printf("jsonrpc2: <-- request #%s: %s: %s\n", req.ID, req.Method, params) } case resp != nil: mu.Lock() method := reqMethods[resp.ID] delete(reqMethods, resp.ID) mu.Unlock() if method == "" { method = "(no previous request)" } if resp.Result != nil { result, _ := json.Marshal(resp.Result) logger.Printf("jsonrpc2: <-- result #%s: %s: %s\n", resp.ID, method, result) } else { err, _ := json.Marshal(resp.Error) logger.Printf("jsonrpc2: <-- error #%s: %s: %s\n", resp.ID, method, err) } } })(c) } } // SetLogger sets the logger for the connection. func SetLogger(logger Logger) ConnOpt { return func(c *Conn) { c.logger = logger } } jsonrpc2-0.2.0/conn_opt_test.go000066400000000000000000000020141437535403000164570ustar00rootroot00000000000000package jsonrpc2_test import ( "bufio" "context" "io" "log" "net" "testing" "github.com/sourcegraph/jsonrpc2" ) func TestSetLogger(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() rd, wr := io.Pipe() defer rd.Close() defer wr.Close() buf := bufio.NewReader(rd) logger := log.New(wr, "", log.Lmsgprefix) a, b := net.Pipe() connA := jsonrpc2.NewConn( ctx, jsonrpc2.NewBufferedStream(a, jsonrpc2.VSCodeObjectCodec{}), noopHandler{}, jsonrpc2.SetLogger(logger), ) connB := jsonrpc2.NewConn( ctx, jsonrpc2.NewBufferedStream(b, jsonrpc2.VSCodeObjectCodec{}), noopHandler{}, ) defer connA.Close() defer connB.Close() // Write a response with no corresponding request. if err := connB.Reply(ctx, jsonrpc2.ID{Num: 0}, nil); err != nil { t.Fatal(err) } want := "jsonrpc2: ignoring response #0 with no corresponding request\n" got, err := buf.ReadString('\n') if err != nil { t.Fatal(err) } if got != want { t.Fatalf("got %q, want %q", got, want) } } jsonrpc2-0.2.0/conn_test.go000066400000000000000000000121501437535403000155770ustar00rootroot00000000000000package jsonrpc2_test import ( "context" "encoding/json" "fmt" "io" "log" "net" "sync" "testing" "time" "github.com/sourcegraph/jsonrpc2" ) var paramsTests = []struct { sendParams interface{} wantParams *json.RawMessage }{ { sendParams: nil, wantParams: nil, }, { sendParams: jsonNull, wantParams: &jsonNull, }, { sendParams: false, wantParams: rawJSONMessage("false"), }, { sendParams: 0, wantParams: rawJSONMessage("0"), }, { sendParams: "", wantParams: rawJSONMessage(`""`), }, { sendParams: rawJSONMessage(`{"foo":"bar"}`), wantParams: rawJSONMessage(`{"foo":"bar"}`), }, } func TestConn_DispatchCall(t *testing.T) { for _, test := range paramsTests { t.Run(fmt.Sprintf("%s", test.sendParams), func(t *testing.T) { testParams(t, test.wantParams, func(c *jsonrpc2.Conn) error { _, err := c.DispatchCall(context.Background(), "f", test.sendParams) return err }) }) } } func TestConn_Notify(t *testing.T) { for _, test := range paramsTests { t.Run(fmt.Sprintf("%s", test.sendParams), func(t *testing.T) { testParams(t, test.wantParams, func(c *jsonrpc2.Conn) error { return c.Notify(context.Background(), "f", test.sendParams) }) }) } } func TestConn_DisconnectNotify(t *testing.T) { t.Run("EOF", func(t *testing.T) { connA, connB := net.Pipe() c := jsonrpc2.NewConn(context.Background(), jsonrpc2.NewPlainObjectStream(connB), nil) // By closing connA, connB receives io.EOF if err := connA.Close(); err != nil { t.Error(err) } assertDisconnect(t, c, connB) }) t.Run("Close", func(t *testing.T) { _, connB := net.Pipe() c := jsonrpc2.NewConn(context.Background(), jsonrpc2.NewPlainObjectStream(connB), nil) if err := c.Close(); err != nil { t.Error(err) } assertDisconnect(t, c, connB) }) t.Run("Close async", func(t *testing.T) { done := make(chan struct{}) _, connB := net.Pipe() c := jsonrpc2.NewConn(context.Background(), jsonrpc2.NewPlainObjectStream(connB), nil) go func() { if err := c.Close(); err != nil && err != jsonrpc2.ErrClosed { t.Error(err) } close(done) }() assertDisconnect(t, c, connB) <-done }) t.Run("protocol error", func(t *testing.T) { connA, connB := net.Pipe() c := jsonrpc2.NewConn( context.Background(), jsonrpc2.NewPlainObjectStream(connB), noopHandler{}, // Suppress log message. This connection receives an invalid JSON // message that causes an error to be written to the logger. We // don't want this expected error to appear in os.Stderr though when // running tests in verbose mode or when other tests fail. jsonrpc2.SetLogger(log.New(io.Discard, "", 0)), ) connA.Write([]byte("invalid json")) assertDisconnect(t, c, connB) }) } func TestConn_Close(t *testing.T) { t.Run("waiting for response", func(t *testing.T) { connA, connB := net.Pipe() nodeA := jsonrpc2.NewConn( context.Background(), jsonrpc2.NewPlainObjectStream(connA), noopHandler{}, ) defer nodeA.Close() nodeB := jsonrpc2.NewConn( context.Background(), jsonrpc2.NewPlainObjectStream(connB), noopHandler{}, ) defer nodeB.Close() ready := make(chan struct{}) done := make(chan struct{}) go func() { close(ready) err := nodeB.Call(context.Background(), "m", nil, nil) if err != jsonrpc2.ErrClosed { t.Errorf("got error %v, want %v", err, jsonrpc2.ErrClosed) } close(done) }() // Wait for the request to be sent before we close the connection. <-ready if err := nodeB.Close(); err != nil && err != jsonrpc2.ErrClosed { t.Error(err) } assertDisconnect(t, nodeB, connB) <-done }) } func testParams(t *testing.T, want *json.RawMessage, fn func(c *jsonrpc2.Conn) error) { wg := &sync.WaitGroup{} handler := handlerFunc(func(ctx context.Context, conn *jsonrpc2.Conn, r *jsonrpc2.Request) { assertRawJSONMessage(t, r.Params, want) wg.Done() }) client, server := newClientServer(handler) defer client.Close() defer server.Close() wg.Add(1) if err := fn(client); err != nil { t.Error(err) } wg.Wait() } func assertDisconnect(t *testing.T, c *jsonrpc2.Conn, conn io.Writer) { select { case <-c.DisconnectNotify(): case <-time.After(200 * time.Millisecond): t.Error("no disconnect notification") return } // Assert that conn is closed by trying to write to it. _, got := conn.Write(nil) want := io.ErrClosedPipe if got != want { t.Errorf("got %s, want %s", got, want) } } func assertRawJSONMessage(t *testing.T, got *json.RawMessage, want *json.RawMessage) { // Assert pointers. if got == nil || want == nil { if got != want { t.Errorf("pointer: got %s, want %s", got, want) } return } { // If pointers are not nil, then assert values. got := string(*got) want := string(*want) if got != want { t.Errorf("value: got %q, want %q", got, want) } } } func newClientServer(handler jsonrpc2.Handler) (client *jsonrpc2.Conn, server *jsonrpc2.Conn) { ctx := context.Background() connA, connB := net.Pipe() client = jsonrpc2.NewConn( ctx, jsonrpc2.NewPlainObjectStream(connA), noopHandler{}, ) server = jsonrpc2.NewConn( ctx, jsonrpc2.NewPlainObjectStream(connB), handler, ) return client, server } jsonrpc2-0.2.0/example_test.go000066400000000000000000000035221437535403000163000ustar00rootroot00000000000000package jsonrpc2_test import ( "context" "encoding/json" "fmt" "net" "os" "github.com/sourcegraph/jsonrpc2" ) // Send a JSON-RPC notification with its params member omitted. func ExampleConn_Notify_paramsOmitted() { ctx := context.Background() connA, connB := net.Pipe() defer connA.Close() defer connB.Close() rpcConn := jsonrpc2.NewConn(ctx, jsonrpc2.NewPlainObjectStream(connA), nil) // Send the JSON-RPC notification. go func() { // Set params to nil. if err := rpcConn.Notify(ctx, "foo", nil); err != nil { fmt.Fprintln(os.Stderr, "notify:", err) } }() // Read the raw JSON-RPC notification on connB. // // Reading the raw JSON-RPC request is for the purpose of this example only. // Use a jsonrpc2.Handler to read parsed requests. buf := make([]byte, 64) n, err := connB.Read(buf) if err != nil { fmt.Fprintln(os.Stderr, "read:", err) } fmt.Printf("%s\n", buf[:n]) // Output: {"jsonrpc":"2.0","method":"foo"} } // Send a JSON-RPC notification with its params member set to null. func ExampleConn_Notify_nullParams() { ctx := context.Background() connA, connB := net.Pipe() defer connA.Close() defer connB.Close() rpcConn := jsonrpc2.NewConn(ctx, jsonrpc2.NewPlainObjectStream(connA), nil) // Send the JSON-RPC notification. go func() { // Set params to the JSON null value. params := json.RawMessage("null") if err := rpcConn.Notify(ctx, "foo", params); err != nil { fmt.Fprintln(os.Stderr, "notify:", err) } }() // Read the raw JSON-RPC notification on connB. // // Reading the raw JSON-RPC request is for the purpose of this example only. // Use a jsonrpc2.Handler to read parsed requests. buf := make([]byte, 64) n, err := connB.Read(buf) if err != nil { fmt.Fprintln(os.Stderr, "read:", err) } fmt.Printf("%s\n", buf[:n]) // Output: {"jsonrpc":"2.0","method":"foo","params":null} } jsonrpc2-0.2.0/go.mod000066400000000000000000000001351437535403000143620ustar00rootroot00000000000000module github.com/sourcegraph/jsonrpc2 go 1.12 require github.com/gorilla/websocket v1.4.1 jsonrpc2-0.2.0/go.sum000066400000000000000000000002571437535403000144140ustar00rootroot00000000000000github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= jsonrpc2-0.2.0/handler_with_error.go000066400000000000000000000042151437535403000174670ustar00rootroot00000000000000package jsonrpc2 import ( "context" ) // HandlerWithError implements Handler by calling the func for each // request and handling returned errors and results. func HandlerWithError(handleFunc func(context.Context, *Conn, *Request) (result interface{}, err error)) *HandlerWithErrorConfigurer { return &HandlerWithErrorConfigurer{handleFunc: handleFunc} } // HandlerWithErrorConfigurer is a handler created by HandlerWithError. type HandlerWithErrorConfigurer struct { handleFunc func(context.Context, *Conn, *Request) (result interface{}, err error) suppressErrClosed bool } // Handle implements Handler. func (h *HandlerWithErrorConfigurer) Handle(ctx context.Context, conn *Conn, req *Request) { result, err := h.handleFunc(ctx, conn, req) if req.Notif { if err != nil { conn.logger.Printf("jsonrpc2 handler: notification %q handling error: %v\n", req.Method, err) } return } resp := &Response{ID: req.ID} if err == nil { err = resp.SetResult(result) } if err != nil { if e, ok := err.(*Error); ok { resp.Error = e } else { resp.Error = &Error{Message: err.Error()} } } if !req.Notif { if err := conn.SendResponse(ctx, resp); err != nil { if err != ErrClosed || !h.suppressErrClosed { conn.logger.Printf("jsonrpc2 handler: sending response %s: %v\n", resp.ID, err) } } } } // SuppressErrClosed makes the handler suppress jsonrpc2.ErrClosed errors from // being logged. The original handler `h` is returned. // // This is optional because only in some cases is this behavior desired. For // example, a handler that serves end-user connections may not want to log // ErrClosed because it just indicates the end-user connection has gone away // for any reason (they could have lost wifi connection, are no longer // interested in the request and closed the connection, etc) and as such it // would be log spam, whereas a handler that serves internal connections would // never expect connections to go away unexpectedly (which could indicate // service degradation, etc) and as such ErrClosed should always be logged. func (h *HandlerWithErrorConfigurer) SuppressErrClosed() Handler { h.suppressErrClosed = true return h } jsonrpc2-0.2.0/internal_test.go000066400000000000000000000017331437535403000164630ustar00rootroot00000000000000package jsonrpc2 import ( "encoding/json" "testing" ) func TestAnyMessage(t *testing.T) { tests := map[string]struct { request, response, invalid bool }{ // Single messages `{}`: {invalid: true}, `{"foo":"bar"}`: {invalid: true}, `{"method":"m"}`: {request: true}, `{"result":123}`: {response: true}, `{"result":null}`: {response: true}, `{"error":{"code":456,"message":"m"}}`: {response: true}, } for s, want := range tests { var m anyMessage if err := json.Unmarshal([]byte(s), &m); err != nil { if !want.invalid { t.Errorf("%s: error: %v", s, err) } continue } if (m.request != nil) != want.request { t.Errorf("%s: got request %v, want %v", s, m.request != nil, want.request) } if (m.response != nil) != want.response { t.Errorf("%s: got response %v, want %v", s, m.response != nil, want.response) } } } jsonrpc2-0.2.0/jsonrpc2.go000066400000000000000000000066401437535403000153520ustar00rootroot00000000000000// Package jsonrpc2 provides a client and server implementation of // [JSON-RPC 2.0](http://www.jsonrpc.org/specification). package jsonrpc2 import ( "context" "encoding/json" "errors" "fmt" "strconv" ) // JSONRPC2 describes an interface for issuing requests that speak the // JSON-RPC 2 protocol. It isn't really necessary for this package // itself, but is useful for external users that use the interface as // an API boundary. type JSONRPC2 interface { // Call issues a standard request (http://www.jsonrpc.org/specification#request_object). Call(ctx context.Context, method string, params, result interface{}, opt ...CallOption) error // Notify issues a notification request (http://www.jsonrpc.org/specification#notification). Notify(ctx context.Context, method string, params interface{}, opt ...CallOption) error // Close closes the underlying connection, if it exists. Close() error } // Error represents a JSON-RPC response error. type Error struct { Code int64 `json:"code"` Message string `json:"message"` Data *json.RawMessage `json:"data,omitempty"` } // SetError sets e.Data to the JSON encoding of v. If JSON // marshaling fails, it panics. func (e *Error) SetError(v interface{}) { b, err := json.Marshal(v) if err != nil { panic("Error.SetData: " + err.Error()) } e.Data = (*json.RawMessage)(&b) } // Error implements the Go error interface. func (e *Error) Error() string { return fmt.Sprintf("jsonrpc2: code %v message: %s", e.Code, e.Message) } // Errors defined in the JSON-RPC spec. See // http://www.jsonrpc.org/specification#error_object. const ( CodeParseError = -32700 CodeInvalidRequest = -32600 CodeMethodNotFound = -32601 CodeInvalidParams = -32602 CodeInternalError = -32603 ) // Handler handles JSON-RPC requests and notifications. type Handler interface { // Handle is called to handle a request. No other requests are handled // until it returns. If you do not require strict ordering behavior // of received RPCs, it is suggested to wrap your handler in // AsyncHandler. Handle(context.Context, *Conn, *Request) } // ID represents a JSON-RPC 2.0 request ID, which may be either a // string or number (or null, which is unsupported). type ID struct { // At most one of Num or Str may be nonzero. If both are zero // valued, then IsNum specifies which field's value is to be used // as the ID. Num uint64 Str string // IsString controls whether the Num or Str field's value should be // used as the ID, when both are zero valued. It must always be // set to true if the request ID is a string. IsString bool } func (id ID) String() string { if id.IsString { return strconv.Quote(id.Str) } return strconv.FormatUint(id.Num, 10) } // MarshalJSON implements json.Marshaler. func (id ID) MarshalJSON() ([]byte, error) { if id.IsString { return json.Marshal(id.Str) } return json.Marshal(id.Num) } // UnmarshalJSON implements json.Unmarshaler. func (id *ID) UnmarshalJSON(data []byte) error { // Support both uint64 and string IDs. var v uint64 if err := json.Unmarshal(data, &v); err == nil { *id = ID{Num: v} return nil } var v2 string if err := json.Unmarshal(data, &v2); err != nil { return err } *id = ID{Str: v2, IsString: true} return nil } // ErrClosed indicates that the JSON-RPC connection is closed (or in // the process of closing). var ErrClosed = errors.New("jsonrpc2: connection is closed") var jsonNull = json.RawMessage("null") jsonrpc2-0.2.0/jsonrpc2_test.go000066400000000000000000000204131437535403000164030ustar00rootroot00000000000000package jsonrpc2_test import ( "context" "encoding/json" "fmt" "io" "net" "net/http" "net/http/httptest" "strings" "sync" "testing" "time" "github.com/gorilla/websocket" "github.com/sourcegraph/jsonrpc2" websocketjsonrpc2 "github.com/sourcegraph/jsonrpc2/websocket" ) func TestError_MarshalJSON(t *testing.T) { tests := []struct { name string setError func(err *jsonrpc2.Error) want string }{ { name: "Data == nil", want: `{"code":-32603,"message":"Internal error"}`, }, { name: "Error.SetError(nil)", setError: func(err *jsonrpc2.Error) { err.SetError(nil) }, want: `{"code":-32603,"message":"Internal error","data":null}`, }, { name: "Error.SetError(0)", setError: func(err *jsonrpc2.Error) { err.SetError(0) }, want: `{"code":-32603,"message":"Internal error","data":0}`, }, { name: `Error.SetError("")`, setError: func(err *jsonrpc2.Error) { err.SetError("") }, want: `{"code":-32603,"message":"Internal error","data":""}`, }, { name: `Error.SetError(false)`, setError: func(err *jsonrpc2.Error) { err.SetError(false) }, want: `{"code":-32603,"message":"Internal error","data":false}`, }, } for _, test := range tests { e := &jsonrpc2.Error{ Code: jsonrpc2.CodeInternalError, Message: "Internal error", } if test.setError != nil { test.setError(e) } b, err := json.Marshal(e) if err != nil { t.Error(err) } got := string(b) if got != test.want { t.Fatalf("%s: got %q, want %q", test.name, got, test.want) } } } // testHandlerA is the "server" handler. type testHandlerA struct{ t *testing.T } func (h *testHandlerA) Handle(ctx context.Context, conn *jsonrpc2.Conn, req *jsonrpc2.Request) { if req.Notif { return // notification } if err := conn.Reply(ctx, req.ID, fmt.Sprintf("hello, #%s: %s", req.ID, *req.Params)); err != nil { h.t.Error(err) } if err := conn.Notify(ctx, "m", fmt.Sprintf("notif for #%s", req.ID)); err != nil { h.t.Error(err) } } // testHandlerB is the "client" handler. type testHandlerB struct { t *testing.T mu sync.Mutex got []string } func (h *testHandlerB) Handle(ctx context.Context, conn *jsonrpc2.Conn, req *jsonrpc2.Request) { if req.Notif { h.mu.Lock() defer h.mu.Unlock() h.got = append(h.got, string(*req.Params)) return } h.t.Errorf("testHandlerB got unexpected request %+v", req) } type streamMaker func(conn io.ReadWriteCloser) jsonrpc2.ObjectStream func testClientServerForCodec(t *testing.T, streamMaker streamMaker) { ctx := context.Background() done := make(chan struct{}) lis, err := net.Listen("tcp", "127.0.0.1:0") // any available address if err != nil { t.Fatal("Listen:", err) } defer func() { if lis == nil { return // already closed } if err = lis.Close(); err != nil { if !strings.HasSuffix(err.Error(), "use of closed network connection") { t.Fatal(err) } } }() ha := testHandlerA{t: t} go func() { if err = serve(ctx, lis, &ha, streamMaker); err != nil { if !strings.HasSuffix(err.Error(), "use of closed network connection") { t.Error(err) } } close(done) }() conn, err := net.Dial("tcp", lis.Addr().String()) if err != nil { t.Fatal("Dial:", err) } testClientServer(ctx, t, streamMaker(conn)) lis.Close() <-done // ensure Serve's error return (if any) is caught by this test } func TestClientServer(t *testing.T) { t.Run("tcp-varint-object-codec", func(t *testing.T) { testClientServerForCodec(t, func(conn io.ReadWriteCloser) jsonrpc2.ObjectStream { return jsonrpc2.NewBufferedStream(conn, jsonrpc2.VarintObjectCodec{}) }) }) t.Run("tcp-vscode-object-codec", func(t *testing.T) { testClientServerForCodec(t, func(conn io.ReadWriteCloser) jsonrpc2.ObjectStream { return jsonrpc2.NewBufferedStream(conn, jsonrpc2.VSCodeObjectCodec{}) }) }) t.Run("tcp-plain-object-codec", func(t *testing.T) { testClientServerForCodec(t, func(conn io.ReadWriteCloser) jsonrpc2.ObjectStream { return jsonrpc2.NewBufferedStream(conn, jsonrpc2.PlainObjectCodec{}) }) }) t.Run("tcp-plain-object-stream", func(t *testing.T) { testClientServerForCodec(t, func(conn io.ReadWriteCloser) jsonrpc2.ObjectStream { return jsonrpc2.NewPlainObjectStream(conn) }) }) t.Run("websocket", func(t *testing.T) { ctx := context.Background() done := make(chan struct{}) ha := testHandlerA{t: t} upgrader := websocket.Upgrader{ReadBufferSize: 1024, WriteBufferSize: 1024} s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { c, err := upgrader.Upgrade(w, r, nil) if err != nil { t.Fatal(err) } defer c.Close() jc := jsonrpc2.NewConn(r.Context(), websocketjsonrpc2.NewObjectStream(c), &ha) <-jc.DisconnectNotify() close(done) })) defer s.Close() c, resp, err := websocket.DefaultDialer.Dial(strings.Replace(s.URL, "http:", "ws:", 1), nil) if err != nil { t.Fatal(err) } defer resp.Body.Close() defer c.Close() testClientServer(ctx, t, websocketjsonrpc2.NewObjectStream(c)) <-done // keep the test running until the WebSocket disconnects (to avoid missing errors) }) } func testClientServer(ctx context.Context, t *testing.T, stream jsonrpc2.ObjectStream) { hb := testHandlerB{t: t} cc := jsonrpc2.NewConn(ctx, stream, &hb) defer func() { if err := cc.Close(); err != nil { t.Fatal(err) } }() // Simple const n = 100 for i := 0; i < n; i++ { var got string if err := cc.Call(ctx, "f", []int32{1, 2, 3}, &got); err != nil { t.Fatal(err) } if want := fmt.Sprintf("hello, #%d: [1,2,3]", i); got != want { t.Errorf("got result %q, want %q", got, want) } } time.Sleep(100 * time.Millisecond) hb.mu.Lock() got := hb.got hb.mu.Unlock() if len(got) != n { t.Errorf("testHandlerB got %d notifications, want %d", len(hb.got), n) } // Ensure messages are in order since we are not using the async handler. for i, s := range got { want := fmt.Sprintf(`"notif for #%d"`, i) if s != want { t.Fatalf("out of order response. got %q, want %q", s, want) } } } func inMemoryPeerConns() (io.ReadWriteCloser, io.ReadWriteCloser) { sr, cw := io.Pipe() cr, sw := io.Pipe() return &pipeReadWriteCloser{sr, sw}, &pipeReadWriteCloser{cr, cw} } type pipeReadWriteCloser struct { *io.PipeReader *io.PipeWriter } func (c *pipeReadWriteCloser) Close() error { err1 := c.PipeReader.Close() err2 := c.PipeWriter.Close() if err1 != nil { return err1 } return err2 } type handlerFunc func(context.Context, *jsonrpc2.Conn, *jsonrpc2.Request) func (h handlerFunc) Handle(ctx context.Context, conn *jsonrpc2.Conn, req *jsonrpc2.Request) { h(ctx, conn, req) } func TestHandlerBlocking(t *testing.T) { // We send N notifications with an increasing parameter. Since the // handler is blocking, we expect to process the notifications in the // order they are sent. ctx, cancel := context.WithCancel(context.Background()) defer cancel() a, b := inMemoryPeerConns() defer a.Close() defer b.Close() var ( wg sync.WaitGroup params []int ) handler := handlerFunc(func(ctx context.Context, conn *jsonrpc2.Conn, req *jsonrpc2.Request) { var i int _ = json.Unmarshal(*req.Params, &i) // don't need to synchronize access to ids since we should be blocking params = append(params, i) wg.Done() }) connA := jsonrpc2.NewConn(ctx, jsonrpc2.NewBufferedStream(a, jsonrpc2.VSCodeObjectCodec{}), handler) connB := jsonrpc2.NewConn(ctx, jsonrpc2.NewBufferedStream(b, jsonrpc2.VSCodeObjectCodec{}), noopHandler{}) defer connA.Close() defer connB.Close() const n = 100 for i := 0; i < n; i++ { wg.Add(1) if err := connB.Notify(ctx, "f", i); err != nil { t.Fatal(err) } } wg.Wait() if len(params) < n { t.Fatalf("want %d params, got %d", n, len(params)) } for want, got := range params { if want != got { t.Fatalf("want param %d, got %d", want, got) } } } type noopHandler struct{} func (noopHandler) Handle(ctx context.Context, conn *jsonrpc2.Conn, req *jsonrpc2.Request) {} func serve(ctx context.Context, lis net.Listener, h jsonrpc2.Handler, streamMaker streamMaker, opts ...jsonrpc2.ConnOpt) error { for { conn, err := lis.Accept() if err != nil { return err } jsonrpc2.NewConn(ctx, streamMaker(conn), h, opts...) } } func rawJSONMessage(v string) *json.RawMessage { b := []byte(v) return (*json.RawMessage)(&b) } var jsonNull = json.RawMessage("null") jsonrpc2-0.2.0/request.go000066400000000000000000000104051437535403000152740ustar00rootroot00000000000000package jsonrpc2 import ( "bytes" "encoding/json" "errors" "fmt" ) // Request represents a JSON-RPC request or // notification. See // http://www.jsonrpc.org/specification#request_object and // http://www.jsonrpc.org/specification#notification. type Request struct { Method string `json:"method"` Params *json.RawMessage `json:"params,omitempty"` ID ID `json:"id"` Notif bool `json:"-"` // Meta optionally provides metadata to include in the request. // // NOTE: It is not part of spec. However, it is useful for propagating // tracing context, etc. Meta *json.RawMessage `json:"meta,omitempty"` // ExtraFields optionally adds fields to the root of the JSON-RPC request. // // NOTE: It is not part of the spec, but there are other protocols based on // JSON-RPC 2 that require it. ExtraFields []RequestField `json:"-"` } // MarshalJSON implements json.Marshaler and adds the "jsonrpc":"2.0" // property. func (r Request) MarshalJSON() ([]byte, error) { r2 := map[string]interface{}{ "jsonrpc": "2.0", "method": r.Method, } for _, field := range r.ExtraFields { r2[field.Name] = field.Value } if !r.Notif { r2["id"] = &r.ID } if r.Params != nil { r2["params"] = r.Params } if r.Meta != nil { r2["meta"] = r.Meta } return json.Marshal(r2) } // UnmarshalJSON implements json.Unmarshaler. func (r *Request) UnmarshalJSON(data []byte) error { r2 := make(map[string]interface{}) // Detect if the "params" or "meta" fields are JSON "null" or just not // present by seeing if the field gets overwritten to nil. emptyParams := &json.RawMessage{} r2["params"] = emptyParams emptyMeta := &json.RawMessage{} r2["meta"] = emptyMeta decoder := json.NewDecoder(bytes.NewReader(data)) decoder.UseNumber() if err := decoder.Decode(&r2); err != nil { return err } var ok bool r.Method, ok = r2["method"].(string) if !ok { return errors.New("missing method field") } switch { case r2["params"] == nil: r.Params = &jsonNull case r2["params"] == emptyParams: r.Params = nil default: b, err := json.Marshal(r2["params"]) if err != nil { return fmt.Errorf("failed to marshal params: %w", err) } r.Params = (*json.RawMessage)(&b) } switch { case r2["meta"] == nil: r.Meta = &jsonNull case r2["meta"] == emptyMeta: r.Meta = nil default: b, err := json.Marshal(r2["meta"]) if err != nil { return fmt.Errorf("failed to marshal Meta: %w", err) } r.Meta = (*json.RawMessage)(&b) } switch rawID := r2["id"].(type) { case nil: r.ID = ID{} r.Notif = true case string: r.ID = ID{Str: rawID, IsString: true} r.Notif = false case json.Number: id, err := rawID.Int64() if err != nil { return fmt.Errorf("failed to unmarshal ID: %w", err) } r.ID = ID{Num: uint64(id)} r.Notif = false default: return fmt.Errorf("unexpected ID type: %T", rawID) } // Clear the extra fields before populating them again. r.ExtraFields = nil for name, value := range r2 { switch name { case "id", "jsonrpc", "meta", "method", "params": continue } r.ExtraFields = append(r.ExtraFields, RequestField{ Name: name, Value: value, }) } return nil } // SetParams sets r.Params to the JSON encoding of v. If JSON // marshaling fails, it returns an error. func (r *Request) SetParams(v interface{}) error { b, err := json.Marshal(v) if err != nil { return err } r.Params = (*json.RawMessage)(&b) return nil } // SetMeta sets r.Meta to the JSON encoding of v. If JSON // marshaling fails, it returns an error. func (r *Request) SetMeta(v interface{}) error { b, err := json.Marshal(v) if err != nil { return err } r.Meta = (*json.RawMessage)(&b) return nil } // SetExtraField adds an entry to r.ExtraFields, so that it is added to the // JSON encoding of the request, as a way to add arbitrary extensions to // JSON RPC 2.0. If JSON marshaling fails, it returns an error. func (r *Request) SetExtraField(name string, v interface{}) error { switch name { case "id", "jsonrpc", "meta", "method", "params": return fmt.Errorf("invalid extra field %q", name) } r.ExtraFields = append(r.ExtraFields, RequestField{ Name: name, Value: v, }) return nil } // RequestField is a top-level field that can be added to the JSON-RPC request. type RequestField struct { Name string Value interface{} } jsonrpc2-0.2.0/request_test.go000066400000000000000000000032701437535403000163350ustar00rootroot00000000000000package jsonrpc2_test import ( "bytes" "encoding/json" "reflect" "testing" "github.com/sourcegraph/jsonrpc2" ) func TestRequest_MarshalJSON_jsonrpc(t *testing.T) { b, err := json.Marshal(&jsonrpc2.Request{}) if err != nil { t.Fatal(err) } if want := `{"id":0,"jsonrpc":"2.0","method":""}`; string(b) != want { t.Errorf("got %q, want %q", b, want) } } func TestRequest_MarshalUnmarshalJSON(t *testing.T) { obj := json.RawMessage(`{"foo":"bar"}`) tests := []struct { data []byte want jsonrpc2.Request }{ { data: []byte(`{"id":123,"jsonrpc":"2.0","method":"m","params":{"foo":"bar"}}`), want: jsonrpc2.Request{ID: jsonrpc2.ID{Num: 123}, Method: "m", Params: &obj}, }, { data: []byte(`{"id":123,"jsonrpc":"2.0","method":"m","params":null}`), want: jsonrpc2.Request{ID: jsonrpc2.ID{Num: 123}, Method: "m", Params: &jsonNull}, }, { data: []byte(`{"id":123,"jsonrpc":"2.0","method":"m"}`), want: jsonrpc2.Request{ID: jsonrpc2.ID{Num: 123}, Method: "m", Params: nil}, }, { data: []byte(`{"id":123,"jsonrpc":"2.0","method":"m","sessionId":"session"}`), want: jsonrpc2.Request{ID: jsonrpc2.ID{Num: 123}, Method: "m", Params: nil, ExtraFields: []jsonrpc2.RequestField{{Name: "sessionId", Value: "session"}}}, }, } for _, test := range tests { var got jsonrpc2.Request if err := json.Unmarshal(test.data, &got); err != nil { t.Error(err) continue } if !reflect.DeepEqual(got, test.want) { t.Errorf("%q: got %+v, want %+v", test.data, got, test.want) continue } data, err := json.Marshal(got) if err != nil { t.Error(err) continue } if !bytes.Equal(data, test.data) { t.Errorf("got JSON %q, want %q", data, test.data) } } } jsonrpc2-0.2.0/response.go000066400000000000000000000041621437535403000154450ustar00rootroot00000000000000package jsonrpc2 import ( "encoding/json" "errors" ) // Response represents a JSON-RPC response. See // http://www.jsonrpc.org/specification#response_object. type Response struct { ID ID `json:"id"` Result *json.RawMessage `json:"result,omitempty"` Error *Error `json:"error,omitempty"` // Meta optionally provides metadata to include in the response. // // NOTE: It is not part of spec. However, it is useful for propagating // tracing context, etc. Meta *json.RawMessage `json:"meta,omitempty"` // SPEC NOTE: The spec says "If there was an error in detecting // the id in the Request object (e.g. Parse error/Invalid // Request), it MUST be Null." If we made the ID field nullable, // then we'd have to make it a pointer type. For simplicity, we're // ignoring the case where there was an error in detecting the ID // in the Request object. } // MarshalJSON implements json.Marshaler and adds the "jsonrpc":"2.0" // property. func (r Response) MarshalJSON() ([]byte, error) { if (r.Result == nil || len(*r.Result) == 0) && r.Error == nil { return nil, errors.New("can't marshal *jsonrpc2.Response (must have result or error)") } type tmpType Response // avoid infinite MarshalJSON recursion b, err := json.Marshal(tmpType(r)) if err != nil { return nil, err } b = append(b[:len(b)-1], []byte(`,"jsonrpc":"2.0"}`)...) return b, nil } // UnmarshalJSON implements json.Unmarshaler. func (r *Response) UnmarshalJSON(data []byte) error { type tmpType Response // Detect if the "result" field is JSON "null" or just not present // by seeing if the field gets overwritten to nil. *r = Response{Result: &json.RawMessage{}} if err := json.Unmarshal(data, (*tmpType)(r)); err != nil { return err } if r.Result == nil { // JSON "null" r.Result = &jsonNull } else if len(*r.Result) == 0 { r.Result = nil } return nil } // SetResult sets r.Result to the JSON representation of v. If JSON // marshaling fails, it returns an error. func (r *Response) SetResult(v interface{}) error { b, err := json.Marshal(v) if err != nil { return err } r.Result = (*json.RawMessage)(&b) return nil } jsonrpc2-0.2.0/response_test.go000066400000000000000000000051561437535403000165100ustar00rootroot00000000000000package jsonrpc2_test import ( "bytes" "encoding/json" "reflect" "testing" "github.com/sourcegraph/jsonrpc2" ) func TestResponse_MarshalJSON_jsonrpc(t *testing.T) { b, err := json.Marshal(&jsonrpc2.Response{Result: &jsonNull}) if err != nil { t.Fatal(err) } if want := `{"id":0,"result":null,"jsonrpc":"2.0"}`; string(b) != want { t.Errorf("got %q, want %q", b, want) } } func TestResponseMarshalJSON_Notif(t *testing.T) { tests := map[*jsonrpc2.Request]bool{ {ID: jsonrpc2.ID{Num: 0}}: true, {ID: jsonrpc2.ID{Num: 1}}: true, {ID: jsonrpc2.ID{Str: "", IsString: true}}: true, {ID: jsonrpc2.ID{Str: "a", IsString: true}}: true, {Notif: true}: false, } for r, wantIDKey := range tests { b, err := json.Marshal(r) if err != nil { t.Fatal(err) } hasIDKey := bytes.Contains(b, []byte(`"id"`)) if hasIDKey != wantIDKey { t.Errorf("got %s, want contain id key: %v", b, wantIDKey) } } } func TestResponseUnmarshalJSON_Notif(t *testing.T) { tests := map[string]bool{ `{"method":"f","id":0}`: false, `{"method":"f","id":1}`: false, `{"method":"f","id":"a"}`: false, `{"method":"f","id":""}`: false, `{"method":"f"}`: true, } for s, want := range tests { var r jsonrpc2.Request if err := json.Unmarshal([]byte(s), &r); err != nil { t.Fatal(err) } if r.Notif != want { t.Errorf("%s: got %v, want %v", s, r.Notif, want) } } } func TestResponse_MarshalUnmarshalJSON(t *testing.T) { obj := json.RawMessage(`{"foo":"bar"}`) tests := []struct { data []byte want jsonrpc2.Response error bool }{ { data: []byte(`{"id":123,"result":{"foo":"bar"},"jsonrpc":"2.0"}`), want: jsonrpc2.Response{ID: jsonrpc2.ID{Num: 123}, Result: &obj}, }, { data: []byte(`{"id":123,"result":null,"jsonrpc":"2.0"}`), want: jsonrpc2.Response{ID: jsonrpc2.ID{Num: 123}, Result: &jsonNull}, }, { data: []byte(`{"id":123,"jsonrpc":"2.0"}`), want: jsonrpc2.Response{ID: jsonrpc2.ID{Num: 123}, Result: nil}, error: true, // either result or error field must be set }, } for _, test := range tests { var got jsonrpc2.Response if err := json.Unmarshal(test.data, &got); err != nil { t.Error(err) continue } if !reflect.DeepEqual(got, test.want) { t.Errorf("%q: got %+v, want %+v", test.data, got, test.want) continue } data, err := json.Marshal(got) if err != nil { if test.error { continue } t.Error(err) continue } if test.error { t.Errorf("%q: expected error", test.data) continue } if !bytes.Equal(data, test.data) { t.Errorf("got JSON %q, want %q", data, test.data) } } } jsonrpc2-0.2.0/stream.go000066400000000000000000000137651437535403000151130ustar00rootroot00000000000000package jsonrpc2 import ( "bufio" "encoding/binary" "encoding/json" "fmt" "io" "strconv" "strings" "sync" ) // An ObjectStream is a bidirectional stream of JSON-RPC 2.0 objects. type ObjectStream interface { // WriteObject writes a JSON-RPC 2.0 object to the stream. WriteObject(obj interface{}) error // ReadObject reads the next JSON-RPC 2.0 object from the stream // and stores it in the value pointed to by v. ReadObject(v interface{}) error io.Closer } // A bufferedObjectStream is an ObjectStream that uses a buffered // io.ReadWriteCloser to send and receive objects. type bufferedObjectStream struct { conn io.Closer // all writes should go through w, all reads through r w *bufio.Writer r *bufio.Reader codec ObjectCodec mu sync.Mutex } // NewBufferedStream creates a buffered stream from a network // connection (or other similar interface). The underlying // objectStream is used to produce the bytes to write to the stream // for the JSON-RPC 2.0 objects. func NewBufferedStream(conn io.ReadWriteCloser, codec ObjectCodec) ObjectStream { switch v := codec.(type) { case PlainObjectCodec: v.decoder = json.NewDecoder(conn) v.encoder = json.NewEncoder(conn) codec = v } return &bufferedObjectStream{ conn: conn, w: bufio.NewWriter(conn), r: bufio.NewReader(conn), codec: codec, } } // WriteObject implements ObjectStream. func (t *bufferedObjectStream) WriteObject(obj interface{}) error { t.mu.Lock() defer t.mu.Unlock() if err := t.codec.WriteObject(t.w, obj); err != nil { return err } return t.w.Flush() } // ReadObject implements ObjectStream. func (t *bufferedObjectStream) ReadObject(v interface{}) error { return t.codec.ReadObject(t.r, v) } // Close implements ObjectStream. func (t *bufferedObjectStream) Close() error { return t.conn.Close() } // An ObjectCodec specifies how to encode and decode a JSON-RPC 2.0 // object in a stream. type ObjectCodec interface { // WriteObject writes a JSON-RPC 2.0 object to the stream. WriteObject(stream io.Writer, obj interface{}) error // ReadObject reads the next JSON-RPC 2.0 object from the stream // and stores it in the value pointed to by v. ReadObject(stream *bufio.Reader, v interface{}) error } // VarintObjectCodec reads/writes JSON-RPC 2.0 objects with a varint // header that encodes the byte length. type VarintObjectCodec struct{} // WriteObject implements ObjectCodec. func (VarintObjectCodec) WriteObject(stream io.Writer, obj interface{}) error { data, err := json.Marshal(obj) if err != nil { return err } var buf [binary.MaxVarintLen64]byte b := binary.PutUvarint(buf[:], uint64(len(data))) if _, err := stream.Write(buf[:b]); err != nil { return err } if _, err := stream.Write(data); err != nil { return err } return nil } // ReadObject implements ObjectCodec. func (VarintObjectCodec) ReadObject(stream *bufio.Reader, v interface{}) error { b, err := binary.ReadUvarint(stream) if err != nil { return err } return json.NewDecoder(io.LimitReader(stream, int64(b))).Decode(v) } // VSCodeObjectCodec reads/writes JSON-RPC 2.0 objects with // Content-Length and Content-Type headers, as specified by // https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md#base-protocol. type VSCodeObjectCodec struct{} // WriteObject implements ObjectCodec. func (VSCodeObjectCodec) WriteObject(stream io.Writer, obj interface{}) error { data, err := json.Marshal(obj) if err != nil { return err } if _, err := fmt.Fprintf(stream, "Content-Length: %d\r\n\r\n", len(data)); err != nil { return err } if _, err := stream.Write(data); err != nil { return err } return nil } // ReadObject implements ObjectCodec. func (VSCodeObjectCodec) ReadObject(stream *bufio.Reader, v interface{}) error { var contentLength uint64 for { line, err := stream.ReadString('\r') if err != nil { return err } b, err := stream.ReadByte() if err != nil { return err } if b != '\n' { return fmt.Errorf(`jsonrpc2: line endings must be \r\n`) } if line == "\r" { break } if strings.HasPrefix(line, "Content-Length: ") { line = strings.TrimPrefix(line, "Content-Length: ") line = strings.TrimSpace(line) var err error contentLength, err = strconv.ParseUint(line, 10, 32) if err != nil { return err } } } if contentLength == 0 { return fmt.Errorf("jsonrpc2: no Content-Length header found") } return json.NewDecoder(io.LimitReader(stream, int64(contentLength))).Decode(v) } // PlainObjectCodec reads/writes plain JSON-RPC 2.0 objects without a header. // // Deprecated: use NewPlainObjectStream type PlainObjectCodec struct { decoder *json.Decoder encoder *json.Encoder } // WriteObject implements ObjectCodec. func (c PlainObjectCodec) WriteObject(stream io.Writer, v interface{}) error { if c.encoder != nil { return c.encoder.Encode(v) } return json.NewEncoder(stream).Encode(v) } // ReadObject implements ObjectCodec. func (c PlainObjectCodec) ReadObject(stream *bufio.Reader, v interface{}) error { if c.decoder != nil { return c.decoder.Decode(v) } return json.NewDecoder(stream).Decode(v) } // plainObjectStream reads/writes plain JSON-RPC 2.0 objects without a header. type plainObjectStream struct { conn io.Closer decoder *json.Decoder encoder *json.Encoder } // NewPlainObjectStream creates a buffered stream from a network // connection (or other similar interface). The underlying // objectStream produces plain JSON-RPC 2.0 objects without a header. func NewPlainObjectStream(conn io.ReadWriteCloser) ObjectStream { return &plainObjectStream{ conn: conn, encoder: json.NewEncoder(conn), decoder: json.NewDecoder(conn), } } func (os *plainObjectStream) ReadObject(v interface{}) error { return os.decoder.Decode(v) } // WriteObject serializes a value to JSON and writes it to a stream. // Not thread-safe, a user must synchronize writes in a multithreaded environment. func (os *plainObjectStream) WriteObject(v interface{}) error { return os.encoder.Encode(v) } func (os *plainObjectStream) Close() error { return os.conn.Close() } jsonrpc2-0.2.0/websocket/000077500000000000000000000000001437535403000152435ustar00rootroot00000000000000jsonrpc2-0.2.0/websocket/stream.go000066400000000000000000000022001437535403000170570ustar00rootroot00000000000000// Package websocket provides WebSocket transport support for JSON-RPC // 2.0. package websocket import ( "io" ws "github.com/gorilla/websocket" ) // A ObjectStream is a jsonrpc2.ObjectStream that uses a WebSocket to // send and receive JSON-RPC 2.0 objects. type ObjectStream struct { conn *ws.Conn } // NewObjectStream creates a new jsonrpc2.ObjectStream for sending and // receiving JSON-RPC 2.0 objects over a WebSocket. func NewObjectStream(conn *ws.Conn) ObjectStream { return ObjectStream{conn: conn} } // WriteObject implements jsonrpc2.ObjectStream. func (t ObjectStream) WriteObject(obj interface{}) error { return t.conn.WriteJSON(obj) } // ReadObject implements jsonrpc2.ObjectStream. func (t ObjectStream) ReadObject(v interface{}) error { err := t.conn.ReadJSON(v) if e, ok := err.(*ws.CloseError); ok { if e.Code == ws.CloseAbnormalClosure && e.Text == io.ErrUnexpectedEOF.Error() { // Suppress a noisy (but harmless) log message by // unwrapping this error. err = io.ErrUnexpectedEOF } } return err } // Close implements jsonrpc2.ObjectStream. func (t ObjectStream) Close() error { return t.conn.Close() }