package json import ( "encoding/json" "io" "git.loafle.net/commons_go/rpc/codec" "git.loafle.net/commons_go/rpc/protocol" ) // ---------------------------------------------------------------------------- // Request // ---------------------------------------------------------------------------- // serverRequest represents a JSON-RPC request received by the server. type serverRequest struct { // JSON-RPC protocol. Version string `json:"jsonrpc"` // A String containing the name of the method to be invoked. Method string `json:"method"` // A Structured value to pass as arguments to the method. Params *json.RawMessage `json:"params,omitempty"` // The request id. MUST be a string, number or null. // Our implementation will not do type checking for id. // It will be copied as it is. ID *json.RawMessage `json:"id,omitempty"` } // ---------------------------------------------------------------------------- // ServerRequestCodec // ---------------------------------------------------------------------------- // newRequestCodec returns a new ServerRequestCodec. func newServerRequestCodec(r io.Reader, codec codec.Codec) (protocol.ServerRequestCodec, error) { // Decode the request body and check if RPC method is valid. decoder := json.NewDecoder(r) if nil == r { return nil, io.EOF } req := &serverRequest{} err := decoder.Decode(req) if err == io.ErrUnexpectedEOF || err == io.EOF { return nil, err } if err != nil { err = &Error{ Code: E_PARSE, Message: err.Error(), Data: req, } } if req.Version != Version { err = &Error{ Code: E_INVALID_REQ, Message: "jsonrpc must be " + Version, Data: req, } } return &ServerRequestCodec{req: req, err: err, codec: codec}, nil } // ServerRequestCodec decodes and encodes a single request. type ServerRequestCodec struct { req *serverRequest err error codec codec.Codec } // Method returns the RPC method for the current request. // // The method uses a dotted notation as in "Service.Method". func (src *ServerRequestCodec) Method() string { return src.req.Method } // ReadRequest fills the request object for the RPC method. // // ReadRequest parses request parameters in two supported forms in // accordance with http://www.jsonrpc.org/specification#parameter_structures // // by-position: params MUST be an Array, containing the // values in the Server expected order. // // by-name: params MUST be an Object, with member names // that match the Server expected parameter names. The // absence of expected names MAY result in an error being // generated. The names MUST match exactly, including // case, to the method's expected parameters. func (src *ServerRequestCodec) ReadParams(args []interface{}) error { if src.err == nil && src.req.Params != nil { // Note: if src.req.Params is nil it's not an error, it's an optional member. // JSON params structured object. Unmarshal to the args object. // if err := json.Unmarshal(*src.req.Params, &args); err != nil { // // 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. // src.err = &Error{ // Code: E_INVALID_REQ, // Message: err.Error(), // Data: src.req.Params, // } // } raws := make([]json.RawMessage, len(args)) if err := json.Unmarshal(*src.req.Params, &raws); err != nil { src.err = &Error{ Code: E_INVALID_REQ, Message: err.Error(), Data: src.req.Params, } return src.err } for indexI := 0; indexI < len(args); indexI++ { raw := raws[indexI] arg := args[indexI] if err := json.Unmarshal(raw, &arg); err != nil { src.err = &Error{ Code: E_INVALID_REQ, Message: err.Error(), Data: src.req.Params, } return src.err } } } return src.err } func (src *ServerRequestCodec) Params() ([]string, error) { if src.err == nil && src.req.Params != nil { var values []interface{} if err := json.Unmarshal(*src.req.Params, &values); err != nil { src.err = &Error{ Code: E_INVALID_REQ, Message: err.Error(), Data: src.req.Params, } return nil, src.err } var results []string for _, v := range values { switch v := v.(type) { case string: results = append(results, v) default: b, err := json.Marshal(v) if nil != err { src.err = &Error{ Code: E_INVALID_REQ, Message: err.Error(), Data: src.req.Params, } return nil, src.err } results = append(results, string(b)) } } return results, nil } return nil, src.err } // WriteResponse encodes the response and writes it to the ResponseWriter. func (src *ServerRequestCodec) WriteResponse(w io.Writer, reply interface{}) error { res := &serverResponse{Version: Version, Result: reply, ID: src.req.ID} return src.writeServerResponse(w, res) } // WriteError encodes the response and writes it to the ResponseWriter. func (src *ServerRequestCodec) WriteError(w io.Writer, status int, err error) error { jsonErr, ok := err.(*Error) if !ok { jsonErr = &Error{ Code: E_SERVER, Message: err.Error(), } } res := &serverResponse{Version: Version, Error: jsonErr, ID: src.req.ID} return src.writeServerResponse(w, res) } func (src *ServerRequestCodec) writeServerResponse(w io.Writer, res *serverResponse) error { // ID is null for notifications and they don't have a response. if res.ID != nil { encoder := json.NewEncoder(src.codec.Encode(w)) // Not sure in which case will this happen. But seems harmless. if err := encoder.Encode(res); nil != err { return err } } return nil }