Skip to content

Commit

Permalink
internal/jsonrpc2_v2: an updated jsonrpc2 library
Browse files Browse the repository at this point in the history
Change-Id: I609173baa6842d33068a7e9596d54f03d89c5401
Reviewed-on: https://go-review.googlesource.com/c/tools/+/292169
Run-TryBot: Ian Cottrell <iancottrell@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Trust: Ian Cottrell <iancottrell@google.com>
Reviewed-by: Robert Findley <rfindley@google.com>
  • Loading branch information
ianthehat committed Mar 24, 2021
1 parent 52cb772 commit 877f9c4
Show file tree
Hide file tree
Showing 10 changed files with 1,992 additions and 0 deletions.
486 changes: 486 additions & 0 deletions internal/jsonrpc2_v2/conn.go

Large diffs are not rendered by default.

179 changes: 179 additions & 0 deletions internal/jsonrpc2_v2/frame.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
// 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"

errors "golang.org/x/xerrors"
)

// Reader abstracts the transport mechanics from the JSON RPC protocol.
// A Conn reads messages from the reader it was provided on construction,
// and assumes that each call to Read fully transfers a single message,
// or returns an error.
// A reader is not safe for concurrent use, it is expected it will be used by
// a single Conn in a safe manner.
type Reader interface {
// Read gets the next message from the stream.
Read(context.Context) (Message, int64, error)
}

// Writer abstracts the transport mechanics from the JSON RPC protocol.
// A Conn writes messages using the writer it was provided on construction,
// and assumes that each call to Write fully transfers a single message,
// or returns an error.
// A writer is not safe for concurrent use, it is expected it will be used by
// a single Conn in a safe manner.
type Writer interface {
// Write sends a message to the stream.
Write(context.Context, Message) (int64, error)
}

// Framer wraps low level byte readers and writers into jsonrpc2 message
// readers and writers.
// It is responsible for the framing and encoding of messages into wire form.
type Framer interface {
// Reader wraps a byte reader into a message reader.
Reader(rw io.Reader) Reader
// Writer wraps a byte writer into a message writer.
Writer(rw io.Writer) Writer
}

// RawFramer returns a new Framer.
// The messages are sent with no wrapping, and rely on json decode consistency
// to determine message boundaries.
func RawFramer() Framer { return rawFramer{} }

type rawFramer struct{}
type rawReader struct{ in *json.Decoder }
type rawWriter struct{ out io.Writer }

func (rawFramer) Reader(rw io.Reader) Reader {
return &rawReader{in: json.NewDecoder(rw)}
}

func (rawFramer) Writer(rw io.Writer) Writer {
return &rawWriter{out: rw}
}

func (r *rawReader) Read(ctx context.Context) (Message, int64, error) {
select {
case <-ctx.Done():
return nil, 0, ctx.Err()
default:
}
var raw json.RawMessage
if err := r.in.Decode(&raw); err != nil {
return nil, 0, err
}
msg, err := DecodeMessage(raw)
return msg, int64(len(raw)), err
}

func (w *rawWriter) Write(ctx context.Context, msg Message) (int64, error) {
select {
case <-ctx.Done():
return 0, ctx.Err()
default:
}
data, err := EncodeMessage(msg)
if err != nil {
return 0, errors.Errorf("marshaling message: %v", err)
}
n, err := w.out.Write(data)
return int64(n), err
}

// HeaderFramer returns a new Framer.
// The messages are sent with HTTP content length and MIME type headers.
// This is the format used by LSP and others.
func HeaderFramer() Framer { return headerFramer{} }

type headerFramer struct{}
type headerReader struct{ in *bufio.Reader }
type headerWriter struct{ out io.Writer }

func (headerFramer) Reader(rw io.Reader) Reader {
return &headerReader{in: bufio.NewReader(rw)}
}

func (headerFramer) Writer(rw io.Writer) Writer {
return &headerWriter{out: rw}
}

func (r *headerReader) Read(ctx context.Context) (Message, int64, error) {
select {
case <-ctx.Done():
return nil, 0, ctx.Err()
default:
}
var total, length int64
// read the header, stop on the first empty line
for {
line, err := r.in.ReadString('\n')
total += int64(len(line))
if err != nil {
return nil, total, errors.Errorf("failed reading header line: %w", err)
}
line = strings.TrimSpace(line)
// check we have a header line
if line == "" {
break
}
colon := strings.IndexRune(line, ':')
if colon < 0 {
return nil, total, errors.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, total, errors.Errorf("failed parsing Content-Length: %v", value)
}
if length <= 0 {
return nil, total, errors.Errorf("invalid Content-Length: %v", length)
}
default:
// ignoring unknown headers
}
}
if length == 0 {
return nil, total, errors.Errorf("missing Content-Length header")
}
data := make([]byte, length)
n, err := io.ReadFull(r.in, data)
total += int64(n)
if err != nil {
return nil, total, err
}
msg, err := DecodeMessage(data)
return msg, total, err
}

func (w *headerWriter) Write(ctx context.Context, msg Message) (int64, error) {
select {
case <-ctx.Done():
return 0, ctx.Err()
default:
}
data, err := EncodeMessage(msg)
if err != nil {
return 0, errors.Errorf("marshaling message: %v", err)
}
n, err := fmt.Fprintf(w.out, "Content-Length: %v\r\n\r\n", len(data))
total := int64(n)
if err == nil {
n, err = w.out.Write(data)
total += int64(n)
}
return total, err
}
84 changes: 84 additions & 0 deletions internal/jsonrpc2_v2/jsonrpc2.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// 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"
"errors"
)

var (
// ErrIdleTimeout is returned when serving timed out waiting for new connections.
ErrIdleTimeout = errors.New("timed out waiting for new connections")
// ErrNotHandled is returned from a handler to indicate it did not handle the
// message.
ErrNotHandled = errors.New("JSON RPC not handled")
// ErrAsyncResponse is returned from a handler to indicate it will generate a
// response asynchronously.
ErrAsyncResponse = errors.New("JSON RPC asynchronous response")
)

// Preempter handles messages on a connection before they are queued to the main
// handler.
// Primarily this is used for cancel handlers or notifications for which out of
// order processing is not an issue.
type Preempter interface {
// Preempt is invoked for each incoming request before it is queued.
// If the request is a call, it must return a value or an error for the reply.
// Preempt should not block or start any new messages on the connection.
Preempt(ctx context.Context, req *Request) (interface{}, error)
}

// Handler handles messages on a connection.
type Handler interface {
// Handle is invoked for each incoming request.
// If the request is a call, it must return a value or an error for the reply.
Handle(ctx context.Context, req *Request) (interface{}, error)
}

type defaultHandler struct{}

func (defaultHandler) Preempt(context.Context, *Request) (interface{}, error) {
return nil, ErrNotHandled
}

func (defaultHandler) Handle(context.Context, *Request) (interface{}, error) {
return nil, ErrNotHandled
}

// async is a small helper for things with an asynchronous result that you can
// wait for.
type async struct {
ready chan struct{}
errBox chan error
}

func (a *async) init() {
a.ready = make(chan struct{})
a.errBox = make(chan error, 1)
a.errBox <- nil
}

func (a *async) done() {
close(a.ready)
}

func (a *async) wait() error {
<-a.ready
err := <-a.errBox
a.errBox <- err
return err
}

func (a *async) setError(err error) {
storedErr := <-a.errBox
if storedErr == nil {
storedErr = err
}
a.errBox <- storedErr
}
Loading

0 comments on commit 877f9c4

Please sign in to comment.