mirror of
https://github.com/minio/minio.git
synced 2024-12-26 15:15:55 -05:00
86409fa93d
To support this functionality we had to fork the gorilla/rpc package with relevant changes
293 lines
8.5 KiB
Go
293 lines
8.5 KiB
Go
// 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)
|
|
}
|
|
}
|