mirror of
https://github.com/minio/minio.git
synced 2025-11-10 05:59:43 -05:00
add audit/admin trace support for browser requests (#10947)
To support this functionality we had to fork the gorilla/rpc package with relevant changes
This commit is contained in:
83
pkg/rpc/json2/client.go
Normal file
83
pkg/rpc/json2/client.go
Normal file
@@ -0,0 +1,83 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Copyright 2020 MinIO, Inc. All rights reserved.
|
||||
// forked from https://github.com/gorilla/rpc/v2
|
||||
// modified to be used with MinIO under Apache
|
||||
// 2.0 license that can be found in the LICENSE file.
|
||||
|
||||
package json2
|
||||
|
||||
import (
|
||||
"io"
|
||||
"math/rand"
|
||||
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
)
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Request and Response
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// clientRequest represents a JSON-RPC request sent by a client.
|
||||
type clientRequest struct {
|
||||
// JSON-RPC protocol.
|
||||
Version string `json:"jsonrpc"`
|
||||
|
||||
// A String containing the name of the method to be invoked.
|
||||
Method string `json:"method"`
|
||||
|
||||
// Object to pass as request parameter to the method.
|
||||
Params interface{} `json:"params"`
|
||||
|
||||
// The request id. This can be of any type. It is used to match the
|
||||
// response with the request that it is replying to.
|
||||
Id uint64 `json:"id"`
|
||||
}
|
||||
|
||||
// clientResponse represents a JSON-RPC response returned to a client.
|
||||
type clientResponse struct {
|
||||
Version string `json:"jsonrpc"`
|
||||
Result *jsoniter.RawMessage `json:"result"`
|
||||
Error *jsoniter.RawMessage `json:"error"`
|
||||
}
|
||||
|
||||
// EncodeClientRequest encodes parameters for a JSON-RPC client request.
|
||||
func EncodeClientRequest(method string, args interface{}) ([]byte, error) {
|
||||
c := &clientRequest{
|
||||
Version: "2.0",
|
||||
Method: method,
|
||||
Params: args,
|
||||
Id: uint64(rand.Int63()),
|
||||
}
|
||||
var json = jsoniter.ConfigCompatibleWithStandardLibrary
|
||||
return json.Marshal(c)
|
||||
}
|
||||
|
||||
// DecodeClientResponse decodes the response body of a client request into
|
||||
// the interface reply.
|
||||
func DecodeClientResponse(r io.Reader, reply interface{}) error {
|
||||
var c clientResponse
|
||||
var json = jsoniter.ConfigCompatibleWithStandardLibrary
|
||||
if err := json.NewDecoder(r).Decode(&c); err != nil {
|
||||
return err
|
||||
}
|
||||
if c.Error != nil {
|
||||
jsonErr := &Error{}
|
||||
if err := json.Unmarshal(*c.Error, jsonErr); err != nil {
|
||||
return &Error{
|
||||
Code: E_SERVER,
|
||||
Message: string(*c.Error),
|
||||
}
|
||||
}
|
||||
return jsonErr
|
||||
}
|
||||
|
||||
if c.Result == nil {
|
||||
return ErrNullResult
|
||||
}
|
||||
|
||||
return json.Unmarshal(*c.Result, reply)
|
||||
}
|
||||
44
pkg/rpc/json2/error.go
Normal file
44
pkg/rpc/json2/error.go
Normal file
@@ -0,0 +1,44 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Copyright 2020 MinIO, Inc. All rights reserved.
|
||||
// forked from https://github.com/gorilla/rpc/v2
|
||||
// modified to be used with MinIO under Apache
|
||||
// 2.0 license that can be found in the LICENSE file.
|
||||
|
||||
package json2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
type ErrorCode int
|
||||
|
||||
const (
|
||||
E_PARSE ErrorCode = -32700
|
||||
E_INVALID_REQ ErrorCode = -32600
|
||||
E_NO_METHOD ErrorCode = -32601
|
||||
E_BAD_PARAMS ErrorCode = -32602
|
||||
E_INTERNAL ErrorCode = -32603
|
||||
E_SERVER ErrorCode = -32000
|
||||
)
|
||||
|
||||
var ErrNullResult = errors.New("result is null")
|
||||
|
||||
type Error struct {
|
||||
// A Number that indicates the error type that occurred.
|
||||
Code ErrorCode `json:"code"` /* required */
|
||||
|
||||
// A String providing a short description of the error.
|
||||
// The message SHOULD be limited to a concise single sentence.
|
||||
Message string `json:"message"` /* required */
|
||||
|
||||
// A Primitive or Structured value that contains additional information about the error.
|
||||
Data interface{} `json:"data"` /* optional */
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
return e.Message
|
||||
}
|
||||
292
pkg/rpc/json2/json_test.go
Normal file
292
pkg/rpc/json2/json_test.go
Normal file
@@ -0,0 +1,292 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Copyright 2020 MinIO, Inc. All rights reserved.
|
||||
// forked from https://github.com/gorilla/rpc/v2
|
||||
// modified to be used with MinIO under Apache
|
||||
// 2.0 license that can be found in the LICENSE file.
|
||||
|
||||
package json2
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/minio/minio/pkg/rpc"
|
||||
)
|
||||
|
||||
// ResponseRecorder is an implementation of http.ResponseWriter that
|
||||
// records its mutations for later inspection in tests.
|
||||
type ResponseRecorder struct {
|
||||
Code int // the HTTP response code from WriteHeader
|
||||
HeaderMap http.Header // the HTTP response headers
|
||||
Body *bytes.Buffer // if non-nil, the bytes.Buffer to append written data to
|
||||
Flushed bool
|
||||
}
|
||||
|
||||
// NewRecorder returns an initialized ResponseRecorder.
|
||||
func NewRecorder() *ResponseRecorder {
|
||||
return &ResponseRecorder{
|
||||
HeaderMap: make(http.Header),
|
||||
Body: new(bytes.Buffer),
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultRemoteAddr is the default remote address to return in RemoteAddr if
|
||||
// an explicit DefaultRemoteAddr isn't set on ResponseRecorder.
|
||||
const DefaultRemoteAddr = "1.2.3.4"
|
||||
|
||||
// Header returns the response headers.
|
||||
func (rw *ResponseRecorder) Header() http.Header {
|
||||
return rw.HeaderMap
|
||||
}
|
||||
|
||||
// Write always succeeds and writes to rw.Body, if not nil.
|
||||
func (rw *ResponseRecorder) Write(buf []byte) (int, error) {
|
||||
if rw.Body != nil {
|
||||
rw.Body.Write(buf)
|
||||
}
|
||||
if rw.Code == 0 {
|
||||
rw.Code = http.StatusOK
|
||||
}
|
||||
return len(buf), nil
|
||||
}
|
||||
|
||||
// WriteHeader sets rw.Code.
|
||||
func (rw *ResponseRecorder) WriteHeader(code int) {
|
||||
rw.Code = code
|
||||
}
|
||||
|
||||
// Flush sets rw.Flushed to true.
|
||||
func (rw *ResponseRecorder) Flush() {
|
||||
rw.Flushed = true
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
var ErrResponseError = errors.New("response error")
|
||||
var ErrMappedResponseError = errors.New("mapped response error")
|
||||
|
||||
type Service1Request struct {
|
||||
A int
|
||||
B int
|
||||
}
|
||||
|
||||
type Service1NoParamsRequest struct {
|
||||
V string `json:"jsonrpc"`
|
||||
M string `json:"method"`
|
||||
ID uint64 `json:"id"`
|
||||
}
|
||||
|
||||
type Service1ParamsArrayRequest struct {
|
||||
V string `json:"jsonrpc"`
|
||||
P []struct {
|
||||
T string
|
||||
} `json:"params"`
|
||||
M string `json:"method"`
|
||||
ID uint64 `json:"id"`
|
||||
}
|
||||
|
||||
type Service1Response struct {
|
||||
Result int
|
||||
}
|
||||
|
||||
type Service1 struct {
|
||||
}
|
||||
|
||||
const Service1DefaultResponse = 9999
|
||||
|
||||
func (t *Service1) Multiply(r *http.Request, req *Service1Request, res *Service1Response) error {
|
||||
if req.A == 0 && req.B == 0 {
|
||||
// Sentinel value for test with no params.
|
||||
res.Result = Service1DefaultResponse
|
||||
} else {
|
||||
res.Result = req.A * req.B
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Service1) ResponseError(r *http.Request, req *Service1Request, res *Service1Response) error {
|
||||
return ErrResponseError
|
||||
}
|
||||
|
||||
func (t *Service1) MappedResponseError(r *http.Request, req *Service1Request, res *Service1Response) error {
|
||||
return ErrMappedResponseError
|
||||
}
|
||||
|
||||
func execute(t *testing.T, s *rpc.Server, method string, req, res interface{}) error {
|
||||
if !s.HasMethod(method) {
|
||||
t.Fatal("Expected to be registered:", method)
|
||||
}
|
||||
|
||||
buf, _ := EncodeClientRequest(method, req)
|
||||
body := bytes.NewBuffer(buf)
|
||||
r, _ := http.NewRequest("POST", "http://localhost:8080/", body)
|
||||
r.Header.Set("Content-Type", "application/json")
|
||||
|
||||
w := NewRecorder()
|
||||
s.ServeHTTP(w, r)
|
||||
|
||||
return DecodeClientResponse(w.Body, res)
|
||||
}
|
||||
|
||||
func executeRaw(t *testing.T, s *rpc.Server, req interface{}, res interface{}) error {
|
||||
j, _ := json.Marshal(req)
|
||||
r, _ := http.NewRequest("POST", "http://localhost:8080/", bytes.NewBuffer(j))
|
||||
r.Header.Set("Content-Type", "application/json")
|
||||
|
||||
w := NewRecorder()
|
||||
s.ServeHTTP(w, r)
|
||||
|
||||
return DecodeClientResponse(w.Body, res)
|
||||
}
|
||||
|
||||
func executeInvalidJSON(t *testing.T, s *rpc.Server, res interface{}) error {
|
||||
r, _ := http.NewRequest("POST", "http://localhost:8080/", strings.NewReader(`not even a json`))
|
||||
r.Header.Set("Content-Type", "application/json")
|
||||
|
||||
w := NewRecorder()
|
||||
s.ServeHTTP(w, r)
|
||||
|
||||
return DecodeClientResponse(w.Body, res)
|
||||
}
|
||||
|
||||
func TestService(t *testing.T) {
|
||||
s := rpc.NewServer()
|
||||
s.RegisterCodec(NewCodec(), "application/json")
|
||||
s.RegisterService(new(Service1), "")
|
||||
|
||||
var res Service1Response
|
||||
if err := execute(t, s, "Service1.Multiply", &Service1Request{4, 2}, &res); err != nil {
|
||||
t.Error("Expected err to be nil, but got:", err)
|
||||
}
|
||||
if res.Result != 8 {
|
||||
t.Errorf("Wrong response: %v.", res.Result)
|
||||
}
|
||||
|
||||
if err := execute(t, s, "Service1.ResponseError", &Service1Request{4, 2}, &res); err == nil {
|
||||
t.Errorf("Expected to get %q, but got nil", ErrResponseError)
|
||||
} else if err.Error() != ErrResponseError.Error() {
|
||||
t.Errorf("Expected to get %q, but got %q", ErrResponseError, err)
|
||||
}
|
||||
|
||||
// No parameters.
|
||||
res = Service1Response{}
|
||||
if err := executeRaw(t, s, &Service1NoParamsRequest{"2.0", "Service1.Multiply", 1}, &res); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if res.Result != Service1DefaultResponse {
|
||||
t.Errorf("Wrong response: got %v, want %v", res.Result, Service1DefaultResponse)
|
||||
}
|
||||
|
||||
// Parameters as by-position.
|
||||
res = Service1Response{}
|
||||
req := Service1ParamsArrayRequest{
|
||||
V: "2.0",
|
||||
P: []struct {
|
||||
T string
|
||||
}{{
|
||||
T: "test",
|
||||
}},
|
||||
M: "Service1.Multiply",
|
||||
ID: 1,
|
||||
}
|
||||
if err := executeRaw(t, s, &req, &res); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if res.Result != Service1DefaultResponse {
|
||||
t.Errorf("Wrong response: got %v, want %v", res.Result, Service1DefaultResponse)
|
||||
}
|
||||
|
||||
res = Service1Response{}
|
||||
if err := executeInvalidJSON(t, s, &res); err == nil {
|
||||
t.Error("Expected to receive an E_PARSE error, but got nil")
|
||||
} else if jsonRpcErr, ok := err.(*Error); !ok {
|
||||
t.Errorf("Expected to receive an Error, but got %T: %s", err, err)
|
||||
} else if jsonRpcErr.Code != E_PARSE {
|
||||
t.Errorf("Expected to receive an E_PARSE JSON-RPC error (%d) but got %d", E_PARSE, jsonRpcErr.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServiceWithErrorMapper(t *testing.T) {
|
||||
const mappedErrorCode = 100
|
||||
|
||||
// errorMapper maps ErrMappedResponseError to an Error with mappedErrorCode Code, everything else is returned as-is
|
||||
errorMapper := func(err error) error {
|
||||
if err == ErrMappedResponseError {
|
||||
return &Error{
|
||||
Code: mappedErrorCode,
|
||||
Message: err.Error(),
|
||||
}
|
||||
}
|
||||
// Map everything else to E_SERVER
|
||||
return &Error{
|
||||
Code: E_SERVER,
|
||||
Message: err.Error(),
|
||||
}
|
||||
}
|
||||
|
||||
s := rpc.NewServer()
|
||||
s.RegisterCodec(NewCustomCodecWithErrorMapper(rpc.DefaultEncoderSelector, errorMapper), "application/json")
|
||||
s.RegisterService(new(Service1), "")
|
||||
|
||||
var res Service1Response
|
||||
if err := execute(t, s, "Service1.MappedResponseError", &Service1Request{4, 2}, &res); err == nil {
|
||||
t.Errorf("Expected to get a JSON-RPC error, but got nil")
|
||||
} else if jsonRpcErr, ok := err.(*Error); !ok {
|
||||
t.Errorf("Expected to get an *Error, but got %T: %s", err, err)
|
||||
} else if jsonRpcErr.Code != mappedErrorCode {
|
||||
t.Errorf("Expected to get Code %d, but got %d", mappedErrorCode, jsonRpcErr.Code)
|
||||
} else if jsonRpcErr.Message != ErrMappedResponseError.Error() {
|
||||
t.Errorf("Expected to get Message %q, but got %q", ErrMappedResponseError.Error(), jsonRpcErr.Message)
|
||||
}
|
||||
|
||||
// Unmapped error behaves as usual
|
||||
if err := execute(t, s, "Service1.ResponseError", &Service1Request{4, 2}, &res); err == nil {
|
||||
t.Errorf("Expected to get a JSON-RPC error, but got nil")
|
||||
} else if jsonRpcErr, ok := err.(*Error); !ok {
|
||||
t.Errorf("Expected to get an *Error, but got %T: %s", err, err)
|
||||
} else if jsonRpcErr.Code != E_SERVER {
|
||||
t.Errorf("Expected to get Code %d, but got %d", E_SERVER, jsonRpcErr.Code)
|
||||
} else if jsonRpcErr.Message != ErrResponseError.Error() {
|
||||
t.Errorf("Expected to get Message %q, but got %q", ErrResponseError.Error(), jsonRpcErr.Message)
|
||||
}
|
||||
|
||||
// Malformed request without method: our framework tries to return an error: we shouldn't map that one
|
||||
malformedRequest := struct {
|
||||
V string `json:"jsonrpc"`
|
||||
ID string `json:"id"`
|
||||
}{
|
||||
V: "3.0",
|
||||
ID: "any",
|
||||
}
|
||||
if err := executeRaw(t, s, &malformedRequest, &res); err == nil {
|
||||
t.Errorf("Expected to get a JSON-RPC error, but got nil")
|
||||
} else if jsonRpcErr, ok := err.(*Error); !ok {
|
||||
t.Errorf("Expected to get an *Error, but got %T: %s", err, err)
|
||||
} else if jsonRpcErr.Code != E_INVALID_REQ {
|
||||
t.Errorf("Expected to get an E_INVALID_REQ error (%d), but got %d", E_INVALID_REQ, jsonRpcErr.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeNullResult(t *testing.T) {
|
||||
data := `{"jsonrpc": "2.0", "id": 12345, "result": null}`
|
||||
reader := bytes.NewReader([]byte(data))
|
||||
var result interface{}
|
||||
|
||||
err := DecodeClientResponse(reader, &result)
|
||||
|
||||
if err != ErrNullResult {
|
||||
t.Error("Expected err no be ErrNullResult, but got:", err)
|
||||
}
|
||||
|
||||
if result != nil {
|
||||
t.Error("Expected result to be nil, but got:", result)
|
||||
}
|
||||
}
|
||||
239
pkg/rpc/json2/server.go
Normal file
239
pkg/rpc/json2/server.go
Normal file
@@ -0,0 +1,239 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Copyright 2020 MinIO, Inc. All rights reserved.
|
||||
// forked from https://github.com/gorilla/rpc/v2
|
||||
// modified to be used with MinIO under Apache
|
||||
// 2.0 license that can be found in the LICENSE file.
|
||||
|
||||
package json2
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/minio/minio/pkg/rpc"
|
||||
)
|
||||
|
||||
var null = jsoniter.RawMessage([]byte("null"))
|
||||
var Version = "2.0"
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Request and Response
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// 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 *jsoniter.RawMessage `json:"params"`
|
||||
|
||||
// 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 *jsoniter.RawMessage `json:"id"`
|
||||
}
|
||||
|
||||
// serverResponse represents a JSON-RPC response returned by the server.
|
||||
type serverResponse struct {
|
||||
// JSON-RPC protocol.
|
||||
Version string `json:"jsonrpc"`
|
||||
|
||||
// The Object that was returned by the invoked method. This must be null
|
||||
// in case there was an error invoking the method.
|
||||
// As per spec the member will be omitted if there was an error.
|
||||
Result interface{} `json:"result,omitempty"`
|
||||
|
||||
// An Error object if there was an error invoking the method. It must be
|
||||
// null if there was no error.
|
||||
// As per spec the member will be omitted if there was no error.
|
||||
Error *Error `json:"error,omitempty"`
|
||||
|
||||
// This must be the same id as the request it is responding to.
|
||||
ID *jsoniter.RawMessage `json:"id"`
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Codec
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// NewCustomCodec returns a new JSON Codec based on passed encoder selector.
|
||||
func NewCustomCodec(encSel rpc.EncoderSelector) *Codec {
|
||||
return &Codec{encSel: encSel}
|
||||
}
|
||||
|
||||
// NewCustomCodecWithErrorMapper returns a new JSON Codec based on the passed encoder selector
|
||||
// and also accepts an errorMapper function.
|
||||
// The errorMapper function will be called if the Service implementation returns an error, with that
|
||||
// error as a param, replacing it by the value returned by this function. This function is intended
|
||||
// to decouple your service implementation from the codec itself, making possible to return abstract
|
||||
// errors in your service, and then mapping them here to the JSON-RPC error codes.
|
||||
func NewCustomCodecWithErrorMapper(encSel rpc.EncoderSelector, errorMapper func(error) error) *Codec {
|
||||
return &Codec{
|
||||
encSel: encSel,
|
||||
errorMapper: errorMapper,
|
||||
}
|
||||
}
|
||||
|
||||
// NewCodec returns a new JSON Codec.
|
||||
func NewCodec() *Codec {
|
||||
return NewCustomCodec(rpc.DefaultEncoderSelector)
|
||||
}
|
||||
|
||||
// Codec creates a CodecRequest to process each request.
|
||||
type Codec struct {
|
||||
encSel rpc.EncoderSelector
|
||||
errorMapper func(error) error
|
||||
}
|
||||
|
||||
// NewRequest returns a CodecRequest.
|
||||
func (c *Codec) NewRequest(r *http.Request) rpc.CodecRequest {
|
||||
return newCodecRequest(r, c.encSel.Select(r), c.errorMapper)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// CodecRequest
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// newCodecRequest returns a new CodecRequest.
|
||||
func newCodecRequest(r *http.Request, encoder rpc.Encoder, errorMapper func(error) error) rpc.CodecRequest {
|
||||
// Decode the request body and check if RPC method is valid.
|
||||
req := new(serverRequest)
|
||||
var json = jsoniter.ConfigCompatibleWithStandardLibrary
|
||||
err := json.NewDecoder(r.Body).Decode(req)
|
||||
|
||||
if err != nil {
|
||||
err = &Error{
|
||||
Code: E_PARSE,
|
||||
Message: err.Error(),
|
||||
Data: req,
|
||||
}
|
||||
} else if req.Version != Version {
|
||||
err = &Error{
|
||||
Code: E_INVALID_REQ,
|
||||
Message: "jsonrpc must be " + Version,
|
||||
Data: req,
|
||||
}
|
||||
}
|
||||
|
||||
r.Body.Close()
|
||||
return &CodecRequest{request: req, err: err, encoder: encoder, errorMapper: errorMapper}
|
||||
}
|
||||
|
||||
// CodecRequest decodes and encodes a single request.
|
||||
type CodecRequest struct {
|
||||
request *serverRequest
|
||||
err error
|
||||
encoder rpc.Encoder
|
||||
errorMapper func(error) error
|
||||
}
|
||||
|
||||
// Method returns the RPC method for the current request.
|
||||
//
|
||||
// The method uses a dotted notation as in "Service.Method".
|
||||
func (c *CodecRequest) Method() (string, error) {
|
||||
if c.err == nil {
|
||||
return c.request.Method, nil
|
||||
}
|
||||
return "", c.err
|
||||
}
|
||||
|
||||
// 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 (c *CodecRequest) ReadRequest(args interface{}) error {
|
||||
if c.err == nil && c.request.Params != 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.
|
||||
var json = jsoniter.ConfigCompatibleWithStandardLibrary
|
||||
if err := json.Unmarshal(*c.request.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.
|
||||
params := [1]interface{}{args}
|
||||
if err = json.Unmarshal(*c.request.Params, ¶ms); err != nil {
|
||||
c.err = &Error{
|
||||
Code: E_INVALID_REQ,
|
||||
Message: err.Error(),
|
||||
Data: c.request.Params,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return c.err
|
||||
}
|
||||
|
||||
// WriteResponse encodes the response and writes it to the ResponseWriter.
|
||||
func (c *CodecRequest) WriteResponse(w http.ResponseWriter, reply interface{}) {
|
||||
res := &serverResponse{
|
||||
Version: Version,
|
||||
Result: reply,
|
||||
ID: c.request.ID,
|
||||
}
|
||||
c.writeServerResponse(w, res)
|
||||
}
|
||||
|
||||
func (c *CodecRequest) WriteError(w http.ResponseWriter, status int, err error) {
|
||||
err = c.tryToMapIfNotAnErrorAlready(err)
|
||||
jsonErr, ok := err.(*Error)
|
||||
if !ok {
|
||||
jsonErr = &Error{
|
||||
Code: E_SERVER,
|
||||
Message: err.Error(),
|
||||
}
|
||||
}
|
||||
res := &serverResponse{
|
||||
Version: Version,
|
||||
Error: jsonErr,
|
||||
ID: c.request.ID,
|
||||
}
|
||||
c.writeServerResponse(w, res)
|
||||
}
|
||||
|
||||
func (c CodecRequest) tryToMapIfNotAnErrorAlready(err error) error {
|
||||
if _, ok := err.(*Error); ok || c.errorMapper == nil {
|
||||
return err
|
||||
}
|
||||
return c.errorMapper(err)
|
||||
}
|
||||
|
||||
func (c *CodecRequest) writeServerResponse(w http.ResponseWriter, res *serverResponse) {
|
||||
// ID is null for notifications and they don't have a response, unless we couldn't even parse the JSON, in that
|
||||
// case we can't know whether it was intended to be a notification
|
||||
if c.request.ID != nil || isParseErrorResponse(res) {
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
var json = jsoniter.ConfigCompatibleWithStandardLibrary
|
||||
encoder := json.NewEncoder(c.encoder.Encode(w))
|
||||
err := encoder.Encode(res)
|
||||
|
||||
// Not sure in which case will this happen. But seems harmless.
|
||||
if err != nil {
|
||||
rpc.WriteError(w, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func isParseErrorResponse(res *serverResponse) bool {
|
||||
return res != nil && res.Error != nil && res.Error.Code == E_PARSE
|
||||
}
|
||||
|
||||
type EmptyResponse struct {
|
||||
}
|
||||
Reference in New Issue
Block a user