package rpc import ( "encoding/json" "io" "net/http" "github.com/pkg/errors" "go.uber.org/zap" ) const ( jsonRPCVersion = "2.0" ) type ( // Request represents a standard JSON-RPC 2.0 // request: http://www.jsonrpc.org/specification#request_object. Request struct { JSONRPC string `json:"jsonrpc"` Method string `json:"method"` RawParams json.RawMessage `json:"params,omitempty"` RawID json.RawMessage `json:"id,omitempty"` } // Response represents a standard JSON-RPC 2.0 // response: http://www.jsonrpc.org/specification#response_object. Response struct { JSONRPC string `json:"jsonrpc"` Result interface{} `json:"result,omitempty"` Error *Error `json:"error,omitempty"` ID json.RawMessage `json:"id,omitempty"` } ) // NewRequest creates a new Request struct. func NewRequest() *Request { return &Request{ JSONRPC: jsonRPCVersion, } } // DecodeData decodes the given reader into the the request // struct. func (r *Request) DecodeData(data io.ReadCloser) error { defer data.Close() err := json.NewDecoder(data).Decode(r) if err != nil { return errors.Errorf("error parsing JSON payload: %s", err) } if r.JSONRPC != jsonRPCVersion { return errors.Errorf("invalid version, expected 2.0 got: '%s'", r.JSONRPC) } return nil } // Params takes a slice of any type and attempts to bind // the params to it. func (r *Request) Params() (*Params, error) { params := Params{} err := json.Unmarshal(r.RawParams, ¶ms) if err != nil { return nil, errors.Errorf("error parsing params field in payload: %s", err) } return ¶ms, nil } // WriteErrorResponse writes an error response to the ResponseWriter. func (s *Server) WriteErrorResponse(r *Request, w http.ResponseWriter, err error) { jsonErr, ok := err.(*Error) if !ok { jsonErr = NewInternalServerError("Internal server error", err) } response := Response{ JSONRPC: r.JSONRPC, Error: jsonErr, ID: r.RawID, } logFields := []zap.Field{ zap.Error(jsonErr.Cause), zap.String("method", r.Method), } params, err := r.Params() if err == nil { logFields = append(logFields, zap.Any("params", params)) } s.log.Error("Error encountered with rpc request", logFields...) w.WriteHeader(jsonErr.HTTPCode) s.writeServerResponse(r, w, response) } // WriteResponse encodes the response and writes it to the ResponseWriter. func (s *Server) WriteResponse(r *Request, w http.ResponseWriter, result interface{}) { response := Response{ JSONRPC: r.JSONRPC, Result: result, ID: r.RawID, } s.writeServerResponse(r, w, response) } func (s *Server) writeServerResponse(r *Request, w http.ResponseWriter, response Response) { w.Header().Set("Content-Type", "application/json; charset=utf-8") if s.config.EnableCORSWorkaround { w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With") } encoder := json.NewEncoder(w) err := encoder.Encode(response) if err != nil { s.log.Error("Error encountered while encoding response", zap.String("err", err.Error()), zap.String("method", r.Method)) } }