227 lines
5.7 KiB
Go
227 lines
5.7 KiB
Go
|
// SPDX-FileCopyrightText: 2018 The Go Language Server Authors
|
||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||
|
|
||
|
package jsonrpc2
|
||
|
|
||
|
import (
|
||
|
"bufio"
|
||
|
"context"
|
||
|
stdjson "encoding/json"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"strconv"
|
||
|
"strings"
|
||
|
|
||
|
"github.com/segmentio/encoding/json"
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
// HdrContentLength is the HTTP header name of the length of the content part in bytes. This header is required.
|
||
|
// This entity header indicates the size of the entity-body, in bytes, sent to the recipient.
|
||
|
//
|
||
|
// RFC 7230, section 3.3.2: Content-Length:
|
||
|
// https://tools.ietf.org/html/rfc7230#section-3.3.2
|
||
|
HdrContentLength = "Content-Length"
|
||
|
|
||
|
// HeaderContentType is the mime type of the content part. Defaults to "application/vscode-jsonrpc; charset=utf-8".
|
||
|
// This entity header is used to indicate the media type of the resource.
|
||
|
//
|
||
|
// RFC 7231, section 3.1.1.5: Content-Type:
|
||
|
// https://tools.ietf.org/html/rfc7231#section-3.1.1.5
|
||
|
HdrContentType = "Content-Type"
|
||
|
|
||
|
// HeaderContentSeparator is the header and content part separator.
|
||
|
HdrContentSeparator = "\r\n\r\n"
|
||
|
)
|
||
|
|
||
|
// Framer wraps a network connection up into a Stream.
|
||
|
//
|
||
|
// It is responsible for the framing and encoding of messages into wire form.
|
||
|
// NewRawStream and NewStream are implementations of a Framer.
|
||
|
type Framer func(conn io.ReadWriteCloser) Stream
|
||
|
|
||
|
// 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.
|
||
|
//
|
||
|
// A stream is not safe for concurrent use, it is expected it will be used by
|
||
|
// a single Conn in a safe manner.
|
||
|
type Stream interface {
|
||
|
// Read gets the next message from the stream.
|
||
|
Read(context.Context) (Message, int64, error)
|
||
|
|
||
|
// Write sends a message to the stream.
|
||
|
Write(context.Context, Message) (int64, error)
|
||
|
|
||
|
// Close closes the connection.
|
||
|
// Any blocked Read or Write operations will be unblocked and return errors.
|
||
|
Close() error
|
||
|
}
|
||
|
|
||
|
type rawStream struct {
|
||
|
conn io.ReadWriteCloser
|
||
|
in *stdjson.Decoder
|
||
|
}
|
||
|
|
||
|
// NewRawStream returns a Stream built on top of a io.ReadWriteCloser.
|
||
|
//
|
||
|
// The messages are sent with no wrapping, and rely on json decode consistency
|
||
|
// to determine message boundaries.
|
||
|
func NewRawStream(conn io.ReadWriteCloser) Stream {
|
||
|
return &rawStream{
|
||
|
conn: conn,
|
||
|
in: stdjson.NewDecoder(conn), // TODO(zchee): why test fail using segmentio json.Decoder?
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Read implements Stream.Read.
|
||
|
func (s *rawStream) Read(ctx context.Context) (Message, int64, error) {
|
||
|
select {
|
||
|
case <-ctx.Done():
|
||
|
return nil, 0, ctx.Err()
|
||
|
default:
|
||
|
}
|
||
|
|
||
|
var raw stdjson.RawMessage
|
||
|
if err := s.in.Decode(&raw); err != nil {
|
||
|
return nil, 0, fmt.Errorf("decoding raw message: %w", err)
|
||
|
}
|
||
|
|
||
|
msg, err := DecodeMessage(raw)
|
||
|
return msg, int64(len(raw)), err
|
||
|
}
|
||
|
|
||
|
// Write implements Stream.Write.
|
||
|
func (s *rawStream) Write(ctx context.Context, msg Message) (int64, error) {
|
||
|
select {
|
||
|
case <-ctx.Done():
|
||
|
return 0, ctx.Err()
|
||
|
default:
|
||
|
}
|
||
|
|
||
|
data, err := json.Marshal(msg)
|
||
|
if err != nil {
|
||
|
return 0, fmt.Errorf("marshaling message: %w", err)
|
||
|
}
|
||
|
|
||
|
n, err := s.conn.Write(data)
|
||
|
if err != nil {
|
||
|
return 0, fmt.Errorf("write to stream: %w", err)
|
||
|
}
|
||
|
|
||
|
return int64(n), nil
|
||
|
}
|
||
|
|
||
|
// Close implements Stream.Close.
|
||
|
func (s *rawStream) Close() error {
|
||
|
return s.conn.Close()
|
||
|
}
|
||
|
|
||
|
type stream struct {
|
||
|
conn io.ReadWriteCloser
|
||
|
in *bufio.Reader
|
||
|
}
|
||
|
|
||
|
// NewStream returns a Stream built on top of a io.ReadWriteCloser.
|
||
|
//
|
||
|
// The messages are sent with HTTP content length and MIME type headers.
|
||
|
// This is the format used by LSP and others.
|
||
|
func NewStream(conn io.ReadWriteCloser) Stream {
|
||
|
return &stream{
|
||
|
conn: conn,
|
||
|
in: bufio.NewReader(conn),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Read implements Stream.Read.
|
||
|
func (s *stream) Read(ctx context.Context) (Message, int64, error) {
|
||
|
select {
|
||
|
case <-ctx.Done():
|
||
|
return nil, 0, ctx.Err()
|
||
|
default:
|
||
|
}
|
||
|
|
||
|
var total int64
|
||
|
var length int64
|
||
|
// read the header, stop on the first empty line
|
||
|
for {
|
||
|
line, err := s.in.ReadString('\n')
|
||
|
total += int64(len(line))
|
||
|
if err != nil {
|
||
|
return nil, total, fmt.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, fmt.Errorf("invalid header line %q", line)
|
||
|
}
|
||
|
|
||
|
name, value := line[:colon], strings.TrimSpace(line[colon+1:])
|
||
|
switch name {
|
||
|
case HdrContentLength:
|
||
|
if length, err = strconv.ParseInt(value, 10, 32); err != nil {
|
||
|
return nil, total, fmt.Errorf("failed parsing %s: %v: %w", HdrContentLength, value, err)
|
||
|
}
|
||
|
if length <= 0 {
|
||
|
return nil, total, fmt.Errorf("invalid %s: %v", HdrContentLength, length)
|
||
|
}
|
||
|
default:
|
||
|
// ignoring unknown headers
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if length == 0 {
|
||
|
return nil, total, fmt.Errorf("missing %s header", HdrContentLength)
|
||
|
}
|
||
|
|
||
|
data := make([]byte, length)
|
||
|
if _, err := io.ReadFull(s.in, data); err != nil {
|
||
|
return nil, total, fmt.Errorf("read full of data: %w", err)
|
||
|
}
|
||
|
|
||
|
total += length
|
||
|
msg, err := DecodeMessage(data)
|
||
|
return msg, total, err
|
||
|
}
|
||
|
|
||
|
// Write implements Stream.Write.
|
||
|
func (s *stream) Write(ctx context.Context, msg Message) (int64, error) {
|
||
|
select {
|
||
|
case <-ctx.Done():
|
||
|
return 0, ctx.Err()
|
||
|
default:
|
||
|
}
|
||
|
|
||
|
data, err := json.Marshal(msg)
|
||
|
if err != nil {
|
||
|
return 0, fmt.Errorf("marshaling message: %w", err)
|
||
|
}
|
||
|
|
||
|
n, err := fmt.Fprintf(s.conn, "%s: %v%s", HdrContentLength, len(data), HdrContentSeparator)
|
||
|
total := int64(n)
|
||
|
if err != nil {
|
||
|
return 0, fmt.Errorf("write data to conn: %w", err)
|
||
|
}
|
||
|
|
||
|
n, err = s.conn.Write(data)
|
||
|
total += int64(n)
|
||
|
if err != nil {
|
||
|
return 0, fmt.Errorf("write data to conn: %w", err)
|
||
|
}
|
||
|
|
||
|
return total, nil
|
||
|
}
|
||
|
|
||
|
// Close implements Stream.Close.
|
||
|
func (s *stream) Close() error {
|
||
|
return s.conn.Close()
|
||
|
}
|