mirror of
https://github.com/golang/go.git
synced 2025-05-05 23:53:05 +00:00
internal/jsonrpc2: extract logic to handler hooks
Change-Id: Ief531e4b68fcb0dbc71e263c185fb285a9042479 Reviewed-on: https://go-review.googlesource.com/c/tools/+/185983 Reviewed-by: Rebecca Stambler <rstambler@golang.org>
This commit is contained in:
parent
b32ec66a23
commit
8b927904ee
@ -6,8 +6,6 @@ package jsonrpc2
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Handler is the interface used to hook into the mesage handling of an rpc
|
// Handler is the interface used to hook into the mesage handling of an rpc
|
||||||
@ -38,7 +36,26 @@ type Handler interface {
|
|||||||
// method is the method name specified in the message
|
// method is the method name specified in the message
|
||||||
// payload is the parameters for a call or notification, and the result for a
|
// payload is the parameters for a call or notification, and the result for a
|
||||||
// response
|
// response
|
||||||
Log(direction Direction, id *ID, elapsed time.Duration, method string, payload *json.RawMessage, err *Error)
|
|
||||||
|
// Request is called near the start of processing any request.
|
||||||
|
Request(ctx context.Context, direction Direction, r *WireRequest) context.Context
|
||||||
|
// Response is called near the start of processing any response.
|
||||||
|
Response(ctx context.Context, direction Direction, r *WireResponse) context.Context
|
||||||
|
// Done is called when any request is fully processed.
|
||||||
|
// For calls, this means the response has also been processed, for notifies
|
||||||
|
// this is as soon as the message has been written to the stream.
|
||||||
|
// If err is set, it implies the request failed.
|
||||||
|
Done(ctx context.Context, err error)
|
||||||
|
// Read is called with a count each time some data is read from the stream.
|
||||||
|
// The read calls are delayed until after the data has been interpreted so
|
||||||
|
// that it can be attributed to a request/response.
|
||||||
|
Read(ctx context.Context, bytes int64) context.Context
|
||||||
|
// Wrote is called each time some data is written to the stream.
|
||||||
|
Wrote(ctx context.Context, bytes int64) context.Context
|
||||||
|
// Error is called with errors that cannot be delivered through the normal
|
||||||
|
// mechanisms, for instance a failure to process a notify cannot be delivered
|
||||||
|
// back to the other party.
|
||||||
|
Error(ctx context.Context, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Direction is used to indicate to a logger whether the logged message was being
|
// Direction is used to indicate to a logger whether the logged message was being
|
||||||
@ -73,9 +90,27 @@ func (EmptyHandler) Cancel(ctx context.Context, conn *Conn, id ID, cancelled boo
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (EmptyHandler) Log(direction Direction, id *ID, elapsed time.Duration, method string, payload *json.RawMessage, err *Error) {
|
func (EmptyHandler) Request(ctx context.Context, direction Direction, r *WireRequest) context.Context {
|
||||||
|
return ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (EmptyHandler) Response(ctx context.Context, direction Direction, r *WireResponse) context.Context {
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (EmptyHandler) Done(ctx context.Context, err error) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (EmptyHandler) Read(ctx context.Context, bytes int64) context.Context {
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (EmptyHandler) Wrote(ctx context.Context, bytes int64) context.Context {
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (EmptyHandler) Error(ctx context.Context, err error) {}
|
||||||
|
|
||||||
type defaultHandler struct{ EmptyHandler }
|
type defaultHandler struct{ EmptyHandler }
|
||||||
|
|
||||||
func (defaultHandler) Deliver(ctx context.Context, r *Request, delivered bool) bool {
|
func (defaultHandler) Deliver(ctx context.Context, r *Request, delivered bool) bool {
|
||||||
|
@ -11,6 +11,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
@ -28,7 +29,7 @@ type Conn struct {
|
|||||||
stream Stream
|
stream Stream
|
||||||
err error
|
err error
|
||||||
pendingMu sync.Mutex // protects the pending map
|
pendingMu sync.Mutex // protects the pending map
|
||||||
pending map[ID]chan *wireResponse
|
pending map[ID]chan *WireResponse
|
||||||
handlingMu sync.Mutex // protects the handling map
|
handlingMu sync.Mutex // protects the handling map
|
||||||
handling map[ID]*Request
|
handling map[ID]*Request
|
||||||
}
|
}
|
||||||
@ -47,18 +48,11 @@ const (
|
|||||||
type Request struct {
|
type Request struct {
|
||||||
conn *Conn
|
conn *Conn
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
start time.Time
|
|
||||||
state requestState
|
state requestState
|
||||||
nextRequest chan struct{}
|
nextRequest chan struct{}
|
||||||
|
|
||||||
// Method is a string containing the method name to invoke.
|
// The Wire values of the request.
|
||||||
Method string
|
WireRequest
|
||||||
// Params is either a struct or an array with the parameters of the method.
|
|
||||||
Params *json.RawMessage
|
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type rpcStats struct {
|
type rpcStats struct {
|
||||||
@ -115,9 +109,9 @@ func NewErrorf(code int64, format string, args ...interface{}) *Error {
|
|||||||
// You must call Run for the connection to be active.
|
// You must call Run for the connection to be active.
|
||||||
func NewConn(s Stream) *Conn {
|
func NewConn(s Stream) *Conn {
|
||||||
conn := &Conn{
|
conn := &Conn{
|
||||||
handlers: []Handler{defaultHandler{}},
|
handlers: []Handler{defaultHandler{}, &tracer{}},
|
||||||
stream: s,
|
stream: s,
|
||||||
pending: make(map[ID]chan *wireResponse),
|
pending: make(map[ID]chan *WireResponse),
|
||||||
handling: make(map[ID]*Request),
|
handling: make(map[ID]*Request),
|
||||||
}
|
}
|
||||||
return conn
|
return conn
|
||||||
@ -150,14 +144,11 @@ func (c *Conn) Cancel(id ID) {
|
|||||||
// It will return as soon as the notification has been sent, as no response is
|
// It will return as soon as the notification has been sent, as no response is
|
||||||
// possible.
|
// possible.
|
||||||
func (c *Conn) Notify(ctx context.Context, method string, params interface{}) (err error) {
|
func (c *Conn) Notify(ctx context.Context, method string, params interface{}) (err error) {
|
||||||
ctx, rpcStats := start(ctx, false, method, nil)
|
|
||||||
defer rpcStats.end(ctx, &err)
|
|
||||||
|
|
||||||
jsonParams, err := marshalToRaw(params)
|
jsonParams, err := marshalToRaw(params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("marshalling notify parameters: %v", err)
|
return fmt.Errorf("marshalling notify parameters: %v", err)
|
||||||
}
|
}
|
||||||
request := &wireRequest{
|
request := &WireRequest{
|
||||||
Method: method,
|
Method: method,
|
||||||
Params: jsonParams,
|
Params: jsonParams,
|
||||||
}
|
}
|
||||||
@ -166,10 +157,17 @@ func (c *Conn) Notify(ctx context.Context, method string, params interface{}) (e
|
|||||||
return fmt.Errorf("marshalling notify request: %v", err)
|
return fmt.Errorf("marshalling notify request: %v", err)
|
||||||
}
|
}
|
||||||
for _, h := range c.handlers {
|
for _, h := range c.handlers {
|
||||||
h.Log(Send, nil, -1, request.Method, request.Params, nil)
|
ctx = h.Request(ctx, Send, request)
|
||||||
}
|
}
|
||||||
|
defer func() {
|
||||||
|
for _, h := range c.handlers {
|
||||||
|
h.Done(ctx, err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
n, err := c.stream.Write(ctx, data)
|
n, err := c.stream.Write(ctx, data)
|
||||||
telemetry.SentBytes.Record(ctx, n)
|
for _, h := range c.handlers {
|
||||||
|
ctx = h.Wrote(ctx, n)
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -179,13 +177,11 @@ func (c *Conn) Notify(ctx context.Context, method string, params interface{}) (e
|
|||||||
func (c *Conn) Call(ctx context.Context, method string, params, result interface{}) (err error) {
|
func (c *Conn) Call(ctx context.Context, method string, params, result interface{}) (err error) {
|
||||||
// generate a new request identifier
|
// generate a new request identifier
|
||||||
id := ID{Number: atomic.AddInt64(&c.seq, 1)}
|
id := ID{Number: atomic.AddInt64(&c.seq, 1)}
|
||||||
ctx, rpcStats := start(ctx, false, method, &id)
|
|
||||||
defer rpcStats.end(ctx, &err)
|
|
||||||
jsonParams, err := marshalToRaw(params)
|
jsonParams, err := marshalToRaw(params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("marshalling call parameters: %v", err)
|
return fmt.Errorf("marshalling call parameters: %v", err)
|
||||||
}
|
}
|
||||||
request := &wireRequest{
|
request := &WireRequest{
|
||||||
ID: &id,
|
ID: &id,
|
||||||
Method: method,
|
Method: method,
|
||||||
Params: jsonParams,
|
Params: jsonParams,
|
||||||
@ -195,9 +191,12 @@ func (c *Conn) Call(ctx context.Context, method string, params, result interface
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("marshalling call request: %v", err)
|
return fmt.Errorf("marshalling call request: %v", err)
|
||||||
}
|
}
|
||||||
|
for _, h := range c.handlers {
|
||||||
|
ctx = h.Request(ctx, Send, request)
|
||||||
|
}
|
||||||
// we have to add ourselves to the pending map before we send, otherwise we
|
// we have to add ourselves to the pending map before we send, otherwise we
|
||||||
// are racing the response
|
// are racing the response
|
||||||
rchan := make(chan *wireResponse)
|
rchan := make(chan *WireResponse)
|
||||||
c.pendingMu.Lock()
|
c.pendingMu.Lock()
|
||||||
c.pending[id] = rchan
|
c.pending[id] = rchan
|
||||||
c.pendingMu.Unlock()
|
c.pendingMu.Unlock()
|
||||||
@ -206,14 +205,15 @@ func (c *Conn) Call(ctx context.Context, method string, params, result interface
|
|||||||
c.pendingMu.Lock()
|
c.pendingMu.Lock()
|
||||||
delete(c.pending, id)
|
delete(c.pending, id)
|
||||||
c.pendingMu.Unlock()
|
c.pendingMu.Unlock()
|
||||||
|
for _, h := range c.handlers {
|
||||||
|
h.Done(ctx, err)
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
// now we are ready to send
|
// now we are ready to send
|
||||||
before := time.Now()
|
|
||||||
for _, h := range c.handlers {
|
|
||||||
h.Log(Send, request.ID, -1, request.Method, request.Params, nil)
|
|
||||||
}
|
|
||||||
n, err := c.stream.Write(ctx, data)
|
n, err := c.stream.Write(ctx, data)
|
||||||
telemetry.SentBytes.Record(ctx, n)
|
for _, h := range c.handlers {
|
||||||
|
ctx = h.Wrote(ctx, n)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// sending failed, we will never get a response, so don't leave it pending
|
// sending failed, we will never get a response, so don't leave it pending
|
||||||
return err
|
return err
|
||||||
@ -221,9 +221,8 @@ func (c *Conn) Call(ctx context.Context, method string, params, result interface
|
|||||||
// now wait for the response
|
// now wait for the response
|
||||||
select {
|
select {
|
||||||
case response := <-rchan:
|
case response := <-rchan:
|
||||||
elapsed := time.Since(before)
|
|
||||||
for _, h := range c.handlers {
|
for _, h := range c.handlers {
|
||||||
h.Log(Receive, response.ID, elapsed, request.Method, response.Result, response.Error)
|
ctx = h.Response(ctx, Receive, response)
|
||||||
}
|
}
|
||||||
// is it an error response?
|
// is it an error response?
|
||||||
if response.Error != nil {
|
if response.Error != nil {
|
||||||
@ -283,9 +282,6 @@ func (r *Request) Reply(ctx context.Context, result interface{}, err error) erro
|
|||||||
if r.IsNotify() {
|
if r.IsNotify() {
|
||||||
return fmt.Errorf("reply not invoked with a valid call")
|
return fmt.Errorf("reply not invoked with a valid call")
|
||||||
}
|
}
|
||||||
ctx, close := trace.StartSpan(ctx, r.Method+":reply")
|
|
||||||
defer close()
|
|
||||||
|
|
||||||
// reply ends the handling phase of a call, so if we are not yet
|
// reply ends the handling phase of a call, so if we are not yet
|
||||||
// parallel we should be now. The go routine is allowed to continue
|
// parallel we should be now. The go routine is allowed to continue
|
||||||
// to do work after replying, which is why it is important to unlock
|
// to do work after replying, which is why it is important to unlock
|
||||||
@ -293,12 +289,11 @@ func (r *Request) Reply(ctx context.Context, result interface{}, err error) erro
|
|||||||
r.Parallel()
|
r.Parallel()
|
||||||
r.state = requestReplied
|
r.state = requestReplied
|
||||||
|
|
||||||
elapsed := time.Since(r.start)
|
|
||||||
var raw *json.RawMessage
|
var raw *json.RawMessage
|
||||||
if err == nil {
|
if err == nil {
|
||||||
raw, err = marshalToRaw(result)
|
raw, err = marshalToRaw(result)
|
||||||
}
|
}
|
||||||
response := &wireResponse{
|
response := &WireResponse{
|
||||||
Result: raw,
|
Result: raw,
|
||||||
ID: r.ID,
|
ID: r.ID,
|
||||||
}
|
}
|
||||||
@ -314,10 +309,12 @@ func (r *Request) Reply(ctx context.Context, result interface{}, err error) erro
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, h := range r.conn.handlers {
|
for _, h := range r.conn.handlers {
|
||||||
h.Log(Send, response.ID, elapsed, r.Method, response.Result, response.Error)
|
ctx = h.Response(ctx, Send, response)
|
||||||
}
|
}
|
||||||
n, err := r.conn.stream.Write(ctx, data)
|
n, err := r.conn.stream.Write(ctx, data)
|
||||||
telemetry.SentBytes.Record(ctx, n)
|
for _, h := range r.conn.handlers {
|
||||||
|
ctx = h.Wrote(ctx, n)
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// TODO(iancottrell): if a stream write fails, we really need to shut down
|
// TODO(iancottrell): if a stream write fails, we really need to shut down
|
||||||
@ -374,7 +371,7 @@ func (c *Conn) Run(ctx context.Context) error {
|
|||||||
// a badly formed message arrived, log it and continue
|
// a badly formed message arrived, log it and continue
|
||||||
// we trust the stream to have isolated the error to just this message
|
// we trust the stream to have isolated the error to just this message
|
||||||
for _, h := range c.handlers {
|
for _, h := range c.handlers {
|
||||||
h.Log(Receive, nil, -1, "", nil, NewErrorf(0, "unmarshal failed: %v", err))
|
h.Error(ctx, fmt.Errorf("unmarshal failed: %v", err))
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -382,19 +379,23 @@ func (c *Conn) Run(ctx context.Context) error {
|
|||||||
switch {
|
switch {
|
||||||
case msg.Method != "":
|
case msg.Method != "":
|
||||||
// if method is set it must be a request
|
// if method is set it must be a request
|
||||||
reqCtx, cancelReq := context.WithCancel(ctx)
|
ctx, cancelReq := context.WithCancel(ctx)
|
||||||
reqCtx, rpcStats := start(reqCtx, true, msg.Method, msg.ID)
|
|
||||||
telemetry.ReceivedBytes.Record(ctx, n)
|
|
||||||
thisRequest := nextRequest
|
thisRequest := nextRequest
|
||||||
nextRequest = make(chan struct{})
|
nextRequest = make(chan struct{})
|
||||||
req := &Request{
|
req := &Request{
|
||||||
conn: c,
|
conn: c,
|
||||||
cancel: cancelReq,
|
cancel: cancelReq,
|
||||||
nextRequest: nextRequest,
|
nextRequest: nextRequest,
|
||||||
start: time.Now(),
|
WireRequest: WireRequest{
|
||||||
Method: msg.Method,
|
VersionTag: msg.VersionTag,
|
||||||
Params: msg.Params,
|
Method: msg.Method,
|
||||||
ID: msg.ID,
|
Params: msg.Params,
|
||||||
|
ID: msg.ID,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, h := range c.handlers {
|
||||||
|
ctx = h.Request(ctx, Receive, &req.WireRequest)
|
||||||
|
ctx = h.Read(ctx, n)
|
||||||
}
|
}
|
||||||
c.setHandling(req, true)
|
c.setHandling(req, true)
|
||||||
go func() {
|
go func() {
|
||||||
@ -403,16 +404,17 @@ func (c *Conn) Run(ctx context.Context) error {
|
|||||||
defer func() {
|
defer func() {
|
||||||
c.setHandling(req, false)
|
c.setHandling(req, false)
|
||||||
if !req.IsNotify() && req.state < requestReplied {
|
if !req.IsNotify() && req.state < requestReplied {
|
||||||
req.Reply(reqCtx, nil, NewErrorf(CodeInternalError, "method %q did not reply", req.Method))
|
req.Reply(ctx, nil, NewErrorf(CodeInternalError, "method %q did not reply", req.Method))
|
||||||
}
|
}
|
||||||
req.Parallel()
|
req.Parallel()
|
||||||
rpcStats.end(reqCtx, nil)
|
for _, h := range c.handlers {
|
||||||
|
h.Done(ctx, err)
|
||||||
|
}
|
||||||
cancelReq()
|
cancelReq()
|
||||||
}()
|
}()
|
||||||
delivered := false
|
delivered := false
|
||||||
for _, h := range c.handlers {
|
for _, h := range c.handlers {
|
||||||
h.Log(Receive, req.ID, -1, req.Method, req.Params, nil)
|
if h.Deliver(ctx, req, delivered) {
|
||||||
if h.Deliver(reqCtx, req, delivered) {
|
|
||||||
delivered = true
|
delivered = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -426,7 +428,7 @@ func (c *Conn) Run(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
c.pendingMu.Unlock()
|
c.pendingMu.Unlock()
|
||||||
// and send the reply to the channel
|
// and send the reply to the channel
|
||||||
response := &wireResponse{
|
response := &WireResponse{
|
||||||
Result: msg.Result,
|
Result: msg.Result,
|
||||||
Error: msg.Error,
|
Error: msg.Error,
|
||||||
ID: msg.ID,
|
ID: msg.ID,
|
||||||
@ -435,7 +437,7 @@ func (c *Conn) Run(ctx context.Context) error {
|
|||||||
close(rchan)
|
close(rchan)
|
||||||
default:
|
default:
|
||||||
for _, h := range c.handlers {
|
for _, h := range c.handlers {
|
||||||
h.Log(Receive, nil, -1, "", nil, NewErrorf(0, "message not a call, notify or response, ignoring"))
|
h.Error(ctx, fmt.Errorf("message not a call, notify or response, ignoring"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -449,3 +451,49 @@ func marshalToRaw(obj interface{}) (*json.RawMessage, error) {
|
|||||||
raw := json.RawMessage(data)
|
raw := json.RawMessage(data)
|
||||||
return &raw, nil
|
return &raw, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type statsKeyType int
|
||||||
|
|
||||||
|
const statsKey = statsKeyType(0)
|
||||||
|
|
||||||
|
type tracer struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *tracer) Deliver(ctx context.Context, r *Request, delivered bool) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *tracer) Cancel(ctx context.Context, conn *Conn, id ID, cancelled bool) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *tracer) Request(ctx context.Context, direction Direction, r *WireRequest) context.Context {
|
||||||
|
ctx, stats := start(ctx, direction == Receive, r.Method, r.ID)
|
||||||
|
ctx = context.WithValue(ctx, statsKey, stats)
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *tracer) Response(ctx context.Context, direction Direction, r *WireResponse) context.Context {
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *tracer) Done(ctx context.Context, err error) {
|
||||||
|
stats, ok := ctx.Value(statsKey).(*rpcStats)
|
||||||
|
if ok && stats != nil {
|
||||||
|
stats.end(ctx, &err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *tracer) Read(ctx context.Context, bytes int64) context.Context {
|
||||||
|
telemetry.SentBytes.Record(ctx, bytes)
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *tracer) Wrote(ctx context.Context, bytes int64) context.Context {
|
||||||
|
telemetry.ReceivedBytes.Record(ctx, bytes)
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *tracer) Error(ctx context.Context, err error) {
|
||||||
|
log.Printf("%v", err)
|
||||||
|
}
|
||||||
|
@ -108,7 +108,7 @@ func run(ctx context.Context, t *testing.T, withHeaders bool, r io.ReadCloser, w
|
|||||||
stream = jsonrpc2.NewStream(r, w)
|
stream = jsonrpc2.NewStream(r, w)
|
||||||
}
|
}
|
||||||
conn := jsonrpc2.NewConn(stream)
|
conn := jsonrpc2.NewConn(stream)
|
||||||
conn.AddHandler(handle{})
|
conn.AddHandler(&handle{log: *logRPC})
|
||||||
go func() {
|
go func() {
|
||||||
defer func() {
|
defer func() {
|
||||||
r.Close()
|
r.Close()
|
||||||
@ -121,9 +121,11 @@ func run(ctx context.Context, t *testing.T, withHeaders bool, r io.ReadCloser, w
|
|||||||
return conn
|
return conn
|
||||||
}
|
}
|
||||||
|
|
||||||
type handle struct{ jsonrpc2.EmptyHandler }
|
type handle struct {
|
||||||
|
log bool
|
||||||
|
}
|
||||||
|
|
||||||
func (handle) Deliver(ctx context.Context, r *jsonrpc2.Request, delivered bool) bool {
|
func (h *handle) Deliver(ctx context.Context, r *jsonrpc2.Request, delivered bool) bool {
|
||||||
switch r.Method {
|
switch r.Method {
|
||||||
case "no_args":
|
case "no_args":
|
||||||
if r.Params != nil {
|
if r.Params != nil {
|
||||||
@ -158,18 +160,43 @@ func (handle) Deliver(ctx context.Context, r *jsonrpc2.Request, delivered bool)
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (handle) Log(direction jsonrpc2.Direction, id *jsonrpc2.ID, elapsed time.Duration, method string, payload *json.RawMessage, err *jsonrpc2.Error) {
|
func (h *handle) Cancel(ctx context.Context, conn *jsonrpc2.Conn, id jsonrpc2.ID, cancelled bool) bool {
|
||||||
if !*logRPC {
|
return false
|
||||||
return
|
}
|
||||||
}
|
|
||||||
switch {
|
func (h *handle) Request(ctx context.Context, direction jsonrpc2.Direction, r *jsonrpc2.WireRequest) context.Context {
|
||||||
case err != nil:
|
if h.log {
|
||||||
log.Printf("%v failure [%v] %s %v", direction, id, method, err)
|
if r.ID != nil {
|
||||||
case id == nil:
|
log.Printf("%v call [%v] %s %s", direction, r.ID, r.Method, r.Params)
|
||||||
log.Printf("%v notification %s %s", direction, method, *payload)
|
} else {
|
||||||
case elapsed >= 0:
|
log.Printf("%v notification %s %s", direction, r.Method, r.Params)
|
||||||
log.Printf("%v response in %v [%v] %s %s", direction, elapsed, id, method, *payload)
|
}
|
||||||
default:
|
ctx = context.WithValue(ctx, "method", r.Method)
|
||||||
log.Printf("%v call [%v] %s %s", direction, id, method, *payload)
|
ctx = context.WithValue(ctx, "start", time.Now())
|
||||||
}
|
}
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handle) Response(ctx context.Context, direction jsonrpc2.Direction, r *jsonrpc2.WireResponse) context.Context {
|
||||||
|
if h.log {
|
||||||
|
method := ctx.Value("method")
|
||||||
|
elapsed := time.Since(ctx.Value("start").(time.Time))
|
||||||
|
log.Printf("%v response in %v [%v] %s %s", direction, elapsed, r.ID, method, r.Result)
|
||||||
|
}
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handle) Done(ctx context.Context, err error) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handle) Read(ctx context.Context, bytes int64) context.Context {
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handle) Wrote(ctx context.Context, bytes int64) context.Context {
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handle) Error(ctx context.Context, err error) {
|
||||||
|
log.Printf("%v", err)
|
||||||
}
|
}
|
||||||
|
@ -34,8 +34,8 @@ const (
|
|||||||
CodeServerOverloaded = -32000
|
CodeServerOverloaded = -32000
|
||||||
)
|
)
|
||||||
|
|
||||||
// wireRequest is sent to a server to represent a Call or Notify operaton.
|
// WireRequest is sent to a server to represent a Call or Notify operaton.
|
||||||
type wireRequest struct {
|
type WireRequest struct {
|
||||||
// VersionTag is always encoded as the string "2.0"
|
// VersionTag is always encoded as the string "2.0"
|
||||||
VersionTag VersionTag `json:"jsonrpc"`
|
VersionTag VersionTag `json:"jsonrpc"`
|
||||||
// Method is a string containing the method name to invoke.
|
// Method is a string containing the method name to invoke.
|
||||||
@ -48,11 +48,11 @@ type wireRequest struct {
|
|||||||
ID *ID `json:"id,omitempty"`
|
ID *ID `json:"id,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// wireResponse is a reply to a Request.
|
// WireResponse is a reply to a Request.
|
||||||
// It will always have the ID field set to tie it back to a request, and will
|
// 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
|
// have either the Result or Error fields set depending on whether it is a
|
||||||
// success or failure response.
|
// success or failure response.
|
||||||
type wireResponse struct {
|
type WireResponse struct {
|
||||||
// VersionTag is always encoded as the string "2.0"
|
// VersionTag is always encoded as the string "2.0"
|
||||||
VersionTag VersionTag `json:"jsonrpc"`
|
VersionTag VersionTag `json:"jsonrpc"`
|
||||||
// Result is the response value, and is required on success.
|
// Result is the response value, and is required on success.
|
||||||
|
@ -120,6 +120,18 @@ type handler struct {
|
|||||||
out io.Writer
|
out io.Writer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type rpcStats struct {
|
||||||
|
method string
|
||||||
|
direction jsonrpc2.Direction
|
||||||
|
id *jsonrpc2.ID
|
||||||
|
payload *json.RawMessage
|
||||||
|
start time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
type statsKeyType int
|
||||||
|
|
||||||
|
const statsKey = statsKeyType(0)
|
||||||
|
|
||||||
func (h *handler) Deliver(ctx context.Context, r *jsonrpc2.Request, delivered bool) bool {
|
func (h *handler) Deliver(ctx context.Context, r *jsonrpc2.Request, delivered bool) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -128,7 +140,63 @@ func (h *handler) Cancel(ctx context.Context, conn *jsonrpc2.Conn, id jsonrpc2.I
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) Log(direction jsonrpc2.Direction, id *jsonrpc2.ID, elapsed time.Duration, method string, payload *json.RawMessage, err *jsonrpc2.Error) {
|
func (h *handler) Request(ctx context.Context, direction jsonrpc2.Direction, r *jsonrpc2.WireRequest) context.Context {
|
||||||
|
if !h.trace {
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
stats := &rpcStats{
|
||||||
|
method: r.Method,
|
||||||
|
direction: direction,
|
||||||
|
start: time.Now(),
|
||||||
|
payload: r.Params,
|
||||||
|
}
|
||||||
|
ctx = context.WithValue(ctx, statsKey, stats)
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) Response(ctx context.Context, direction jsonrpc2.Direction, r *jsonrpc2.WireResponse) context.Context {
|
||||||
|
stats := h.getStats(ctx)
|
||||||
|
h.log(direction, r.ID, 0, stats.method, r.Result, nil)
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) Done(ctx context.Context, err error) {
|
||||||
|
if !h.trace {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
stats := h.getStats(ctx)
|
||||||
|
h.log(stats.direction, stats.id, time.Since(stats.start), stats.method, stats.payload, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) Read(ctx context.Context, bytes int64) context.Context {
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) Wrote(ctx context.Context, bytes int64) context.Context {
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
const eol = "\r\n\r\n\r\n"
|
||||||
|
|
||||||
|
func (h *handler) Error(ctx context.Context, err error) {
|
||||||
|
if !h.trace {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
stats := h.getStats(ctx)
|
||||||
|
h.log(stats.direction, stats.id, 0, stats.method, nil, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) getStats(ctx context.Context) *rpcStats {
|
||||||
|
stats, ok := ctx.Value(statsKey).(*rpcStats)
|
||||||
|
if !ok || stats == nil {
|
||||||
|
stats = &rpcStats{
|
||||||
|
method: "???",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return stats
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) log(direction jsonrpc2.Direction, id *jsonrpc2.ID, elapsed time.Duration, method string, payload *json.RawMessage, err error) {
|
||||||
if !h.trace {
|
if !h.trace {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -6,8 +6,6 @@ package protocol
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/tools/internal/jsonrpc2"
|
"golang.org/x/tools/internal/jsonrpc2"
|
||||||
"golang.org/x/tools/internal/lsp/telemetry/trace"
|
"golang.org/x/tools/internal/lsp/telemetry/trace"
|
||||||
@ -17,7 +15,7 @@ import (
|
|||||||
|
|
||||||
type DocumentUri = string
|
type DocumentUri = string
|
||||||
|
|
||||||
type canceller struct{}
|
type canceller struct{ jsonrpc2.EmptyHandler }
|
||||||
|
|
||||||
type clientHandler struct {
|
type clientHandler struct {
|
||||||
canceller
|
canceller
|
||||||
@ -42,9 +40,6 @@ func (canceller) Cancel(ctx context.Context, conn *jsonrpc2.Conn, id jsonrpc2.ID
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (canceller) Log(direction jsonrpc2.Direction, id *jsonrpc2.ID, elapsed time.Duration, method string, payload *json.RawMessage, err *jsonrpc2.Error) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewClient(stream jsonrpc2.Stream, client Client) (*jsonrpc2.Conn, Server, xlog.Logger) {
|
func NewClient(stream jsonrpc2.Stream, client Client) (*jsonrpc2.Conn, Server, xlog.Logger) {
|
||||||
log := xlog.New(NewLogger(client))
|
log := xlog.New(NewLogger(client))
|
||||||
conn := jsonrpc2.NewConn(stream)
|
conn := jsonrpc2.NewConn(stream)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user