blob: 81ca512271baa19a103b42c059ba63fd14513377 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package jsonrpc
import (
"bytes"
"encoding/json"
"fmt"
"io"
"reflect"
"strings"
)
import (
perrors "github.com/pkg/errors"
)
const (
// MAX_JSONRPC_ID max jsonrpc request/response id
MAX_JSONRPC_ID = 0x7FFFFFFF
// VERSION jsonrpc version
VERSION = "2.0"
)
// CodecData is codec data for json RPC.
type CodecData struct {
ID int64
Method string
Args interface{}
Error string
}
const (
// Errors defined in the JSON-RPC spec. See
// http://www.jsonrpc.org/specification#error_object.
CodeParseError = -32700
CodeInvalidRequest = -32600
CodeMethodNotFound = -32601
CodeInvalidParams = -32602
CodeInternalError = -32603
)
// Error response Error
type Error struct {
Code int `json:"code"`
Message string `json:"message"`
Data interface{} `json:"data,omitempty"`
}
// Error decodes response error for a string.
func (e *Error) Error() string {
buf, err := json.Marshal(e)
if err != nil {
msg, retryErr := json.Marshal(err.Error())
if retryErr != nil {
msg = []byte("jsonrpc2.Error: json.Marshal failed")
}
return fmt.Sprintf(`{"code":%d,"message":%s}`, -32001, string(msg))
}
return string(buf)
}
//////////////////////////////////////////
// json client codec
//////////////////////////////////////////
type clientRequest struct {
Version string `json:"jsonrpc"`
Method string `json:"method"`
Params interface{} `json:"params"`
ID int64 `json:"id"`
}
type clientResponse struct {
Version string `json:"jsonrpc"`
ID int64 `json:"id"`
Result *json.RawMessage `json:"result,omitempty"`
Error *Error `json:"error,omitempty"`
}
func (r *clientResponse) reset() {
r.Version = ""
r.ID = 0
r.Result = nil
}
type jsonClientCodec struct {
// temporary work space
req clientRequest
rsp clientResponse
pending map[int64]string
}
func newJsonClientCodec() *jsonClientCodec {
return &jsonClientCodec{
pending: make(map[int64]string),
}
}
// Write codec data as byte.
func (c *jsonClientCodec) Write(d *CodecData) ([]byte, error) {
// If return error: it will be returned as is for this call.
// Allow param to be only Array, Slice, Map or Struct.
// When param is nil or uninitialized Map or Slice - omit "params".
param := d.Args
if param != nil {
switch k := reflect.TypeOf(param).Kind(); k {
case reflect.Map:
if reflect.TypeOf(param).Key().Kind() == reflect.String && reflect.ValueOf(param).IsNil() {
param = nil
}
case reflect.Slice:
if reflect.ValueOf(param).IsNil() {
param = nil
}
case reflect.Array, reflect.Struct:
case reflect.Ptr:
switch ptrK := reflect.TypeOf(param).Elem().Kind(); ptrK {
case reflect.Map:
if reflect.TypeOf(param).Elem().Key().Kind() == reflect.String && reflect.ValueOf(param).Elem().IsNil() {
param = nil
}
case reflect.Slice:
if reflect.ValueOf(param).Elem().IsNil() {
param = nil
}
case reflect.Array, reflect.Struct:
default:
return nil, perrors.New("unsupported param type: Ptr to " + ptrK.String())
}
default:
return nil, perrors.New("unsupported param type: " + k.String())
}
}
c.req.Version = "2.0"
c.req.Method = d.Method
c.req.Params = param
c.req.ID = d.ID & MAX_JSONRPC_ID
// can not use d.ID. otherwise you will get error: can not find method of response id 280698512
c.pending[c.req.ID] = d.Method
buf := bytes.NewBuffer(nil)
defer buf.Reset()
enc := json.NewEncoder(buf)
if err := enc.Encode(&c.req); err != nil {
return nil, perrors.WithStack(err)
}
return buf.Bytes(), nil
}
// Read bytes as structured data
func (c *jsonClientCodec) Read(streamBytes []byte, x interface{}) error {
c.rsp.reset()
buf := bytes.NewBuffer(streamBytes)
defer buf.Reset()
dec := json.NewDecoder(buf)
if err := dec.Decode(&c.rsp); err != nil {
if err != io.EOF {
err = perrors.WithStack(err)
}
return err
}
_, ok := c.pending[c.rsp.ID]
if !ok {
err := perrors.Errorf("can not find method of rsponse id %v, rsponse error:%v", c.rsp.ID, c.rsp.Error)
return err
}
delete(c.pending, c.rsp.ID)
// c.rsp.ID
if c.rsp.Error != nil {
return perrors.New(c.rsp.Error.Error())
}
if c.rsp.Result == nil {
return nil
}
return perrors.WithStack(json.Unmarshal(*c.rsp.Result, x))
}
//////////////////////////////////////////
// json server codec
//////////////////////////////////////////
type serverRequest struct {
Version string `json:"jsonrpc"`
Method string `json:"method"`
Params *json.RawMessage `json:"params"`
ID *json.RawMessage `json:"id"`
}
func (r *serverRequest) reset() {
r.Version = ""
r.Method = ""
if r.Params != nil {
*r.Params = (*r.Params)[:0]
}
if r.ID != nil {
*r.ID = (*r.ID)[:0]
}
}
// UnmarshalJSON unmarshals JSON for server request.
func (r *serverRequest) UnmarshalJSON(raw []byte) error {
r.reset()
type req *serverRequest
// Attention: if do not define a new struct named @req, the json.Unmarshal will invoke
// (*serverRequest)UnmarshalJSON recursively.
if err := json.Unmarshal(raw, req(r)); err != nil {
return perrors.New("bad request")
}
var o = make(map[string]*json.RawMessage)
if err := json.Unmarshal(raw, &o); err != nil {
return perrors.New("bad request")
}
if o["jsonrpc"] == nil || o["method"] == nil {
return perrors.New("bad request")
}
_, okID := o["id"]
_, okParams := o["params"]
if len(o) == 3 && !(okID || okParams) || len(o) == 4 && !(okID && okParams) || len(o) > 4 {
return perrors.New("bad request")
}
if r.Version != Version {
return perrors.New("bad request")
}
if okParams {
if r.Params == nil || len(*r.Params) == 0 {
return perrors.New("bad request")
}
switch []byte(*r.Params)[0] {
case '[', '{':
default:
return perrors.New("bad request")
}
}
if okID && r.ID == nil {
r.ID = &null
}
if okID {
if len(*r.ID) == 0 {
return perrors.New("bad request")
}
switch []byte(*r.ID)[0] {
case 't', 'f', '{', '[':
return perrors.New("bad request")
}
}
return nil
}
type serverResponse struct {
Version string `json:"jsonrpc"`
ID *json.RawMessage `json:"id"`
Result interface{} `json:"result,omitempty"`
Error interface{} `json:"error,omitempty"`
}
// ServerCodec is codec data for request server.
type ServerCodec struct {
req serverRequest
}
var (
null = json.RawMessage([]byte("null"))
// Version is json RPC's version
Version = "2.0"
)
func newServerCodec() *ServerCodec {
return &ServerCodec{}
}
// ReadHeader reads header and unmarshal to server codec
func (c *ServerCodec) ReadHeader(header map[string]string, body []byte) error {
if header["HttpMethod"] != "POST" {
return &Error{Code: -32601, Message: "Method not found"}
}
// If return error:
// - codec will be closed
// So, try to send error reply to client before returning error.
buf := bytes.NewBuffer(body)
defer buf.Reset()
dec := json.NewDecoder(buf)
var raw json.RawMessage
c.req.reset()
if err := dec.Decode(&raw); err != nil {
// rspError := &Error{Code: -32700, Message: "Parse error"}
// c.resp = serverResponse{Version: Version, ID: &null, Error: rspError}
return err
}
if err := json.Unmarshal(raw, &c.req); err != nil {
// if err.Error() == "bad request" {
// rspError := &Error{Code: -32600, Message: "Invalid request"}
// c.resp = serverResponse{Version: Version, ID: &null, Error: rspError}
// }
return err
}
return nil
}
// ReadBody reads @x as request body.
func (c *ServerCodec) ReadBody(x interface{}) error {
// If x!=nil and return error e:
// - Write() will be called with e.Error() in r.Error
if x == nil {
return nil
}
if c.req.Params == nil {
return nil
}
// the request parameter JSON string is converted to the corresponding struct
params := []byte(*c.req.Params)
if err := json.Unmarshal(*c.req.Params, x); err != nil {
// Note: if c.request.Params is nil it's not an error, it's an optional member.
// JSON params structured object. Unmarshal to the args object.
if 2 < len(params) && params[0] == '[' && params[len(params)-1] == ']' {
// Clearly JSON params is not a structured object,
// fallback and attempt an unmarshal with JSON params as
// array value and RPC params is struct. Unmarshal into
// array containing the request struct.
params := [1]interface{}{x}
if err = json.Unmarshal(*c.req.Params, &params); err != nil {
return &Error{Code: -32602, Message: "Invalid params, error:" + err.Error()}
}
} else {
return &Error{Code: -32602, Message: "Invalid params, error:" + err.Error()}
}
}
return nil
}
// NewError creates a error with @code and @message
func NewError(code int, message string) *Error {
return &Error{Code: code, Message: message}
}
func newError(message string) *Error {
switch {
case strings.HasPrefix(message, "rpc: service/method request ill-formed"):
return NewError(-32601, message)
case strings.HasPrefix(message, "rpc: can't find service"):
return NewError(-32601, message)
case strings.HasPrefix(message, "rpc: can't find method"):
return NewError(-32601, message)
default:
return NewError(-32000, message)
}
}
// Write responses as byte
func (c *ServerCodec) Write(errMsg string, x interface{}) ([]byte, error) {
// If return error: nothing happens.
// In r.Error will be "" or .Error() of error returned by:
// - ReadBody()
// - called RPC method
resp := serverResponse{Version: Version, ID: c.req.ID, Result: x}
if len(errMsg) == 0 {
if x == nil {
resp.Result = &null
} else {
resp.Result = x
}
} else if errMsg[0] == '{' && errMsg[len(errMsg)-1] == '}' {
// Well& this check for '{'&'}' isn't too strict, but I
// suppose we're trusting our own RPC methods (this way they
// can force sending wrong reply or many replies instead
// of one) and normal errors won't be formatted this way.
raw := json.RawMessage(errMsg)
resp.Error = &raw
} else {
raw := json.RawMessage(newError(errMsg).Error())
resp.Error = &raw
}
buf := bytes.NewBuffer(nil)
defer buf.Reset()
enc := json.NewEncoder(buf)
if err := enc.Encode(resp); err != nil {
return nil, perrors.WithStack(err)
}
return buf.Bytes(), nil
}