mirror of
https://github.com/minio/minio.git
synced 2024-12-24 22:25:54 -05:00
WebUI: move from rpc/v2/json to rpc/v2/json2 which has better error response structure.
This commit is contained in:
parent
f98675660b
commit
6ad39cb386
@ -22,7 +22,7 @@ import (
|
|||||||
|
|
||||||
router "github.com/gorilla/mux"
|
router "github.com/gorilla/mux"
|
||||||
jsonrpc "github.com/gorilla/rpc/v2"
|
jsonrpc "github.com/gorilla/rpc/v2"
|
||||||
"github.com/gorilla/rpc/v2/json"
|
"github.com/gorilla/rpc/v2/json2"
|
||||||
"github.com/minio/minio-go"
|
"github.com/minio/minio-go"
|
||||||
"github.com/minio/minio/pkg/fs"
|
"github.com/minio/minio/pkg/fs"
|
||||||
"github.com/minio/minio/pkg/probe"
|
"github.com/minio/minio/pkg/probe"
|
||||||
@ -65,7 +65,7 @@ func getWebAPIHandler(web *WebAPI) http.Handler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
s := jsonrpc.NewServer()
|
s := jsonrpc.NewServer()
|
||||||
codec := json.NewCodec()
|
codec := json2.NewCodec()
|
||||||
s.RegisterCodec(codec, "application/json")
|
s.RegisterCodec(codec, "application/json")
|
||||||
s.RegisterCodec(codec, "application/json; charset=UTF-8")
|
s.RegisterCodec(codec, "application/json; charset=UTF-8")
|
||||||
s.RegisterService(web, "Web")
|
s.RegisterService(web, "Web")
|
||||||
|
58
vendor/github.com/gorilla/rpc/v2/json/doc.go
generated
vendored
58
vendor/github.com/gorilla/rpc/v2/json/doc.go
generated
vendored
@ -1,58 +0,0 @@
|
|||||||
// 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.
|
|
||||||
|
|
||||||
/*
|
|
||||||
Package gorilla/rpc/json provides a codec for JSON-RPC over HTTP services.
|
|
||||||
|
|
||||||
To register the codec in a RPC server:
|
|
||||||
|
|
||||||
import (
|
|
||||||
"http"
|
|
||||||
"github.com/gorilla/rpc/v2"
|
|
||||||
"github.com/gorilla/rpc/v2/json"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
s := rpc.NewServer()
|
|
||||||
s.RegisterCodec(json.NewCodec(), "application/json")
|
|
||||||
// [...]
|
|
||||||
http.Handle("/rpc", s)
|
|
||||||
}
|
|
||||||
|
|
||||||
A codec is tied to a content type. In the example above, the server will use
|
|
||||||
the JSON codec for requests with "application/json" as the value for the
|
|
||||||
"Content-Type" header.
|
|
||||||
|
|
||||||
This package follows the JSON-RPC 1.0 specification:
|
|
||||||
|
|
||||||
http://json-rpc.org/wiki/specification
|
|
||||||
|
|
||||||
Request format is:
|
|
||||||
|
|
||||||
method:
|
|
||||||
The name of the method to be invoked, as a string in dotted notation
|
|
||||||
as in "Service.Method".
|
|
||||||
params:
|
|
||||||
An array with a single object to pass as argument to the method.
|
|
||||||
id:
|
|
||||||
The request id, a uint. It is used to match the response with the
|
|
||||||
request that it is replying to.
|
|
||||||
|
|
||||||
Response format is:
|
|
||||||
|
|
||||||
result:
|
|
||||||
The Object that was returned by the invoked method,
|
|
||||||
or null in case there was an error invoking the method.
|
|
||||||
error:
|
|
||||||
An Error object if there was an error invoking the method,
|
|
||||||
or null if there was no error.
|
|
||||||
id:
|
|
||||||
The same id as the request it is responding to.
|
|
||||||
|
|
||||||
Check the gorilla/rpc documentation for more details:
|
|
||||||
|
|
||||||
http://gorilla-web.appspot.com/pkg/rpc
|
|
||||||
*/
|
|
||||||
package json
|
|
132
vendor/github.com/gorilla/rpc/v2/json/json_test.go
generated
vendored
132
vendor/github.com/gorilla/rpc/v2/json/json_test.go
generated
vendored
@ -1,132 +0,0 @@
|
|||||||
// 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.
|
|
||||||
|
|
||||||
package json
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/gorilla/rpc/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrResponseError = errors.New("response error")
|
|
||||||
ErrResponseJsonError = &Error{Data: map[string]interface{}{
|
|
||||||
"stackstrace": map[string]interface{}{"0": "foo()"},
|
|
||||||
"error": "a message",
|
|
||||||
}}
|
|
||||||
)
|
|
||||||
|
|
||||||
type Service1Request struct {
|
|
||||||
A int
|
|
||||||
B int
|
|
||||||
}
|
|
||||||
|
|
||||||
type Service1Response struct {
|
|
||||||
Result int
|
|
||||||
}
|
|
||||||
|
|
||||||
type Service1 struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Service1) Multiply(r *http.Request, req *Service1Request, res *Service1Response) error {
|
|
||||||
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) ResponseJsonError(r *http.Request, req *Service1Request, res *Service1Response) error {
|
|
||||||
return ErrResponseJsonError
|
|
||||||
}
|
|
||||||
|
|
||||||
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 := httptest.NewRecorder()
|
|
||||||
s.ServeHTTP(w, r)
|
|
||||||
|
|
||||||
return DecodeClientResponse(w.Body, res)
|
|
||||||
}
|
|
||||||
|
|
||||||
func executeRaw(t *testing.T, s *rpc.Server, req json.RawMessage) (int, *bytes.Buffer) {
|
|
||||||
r, _ := http.NewRequest("POST", "http://localhost:8080/", bytes.NewBuffer(req))
|
|
||||||
r.Header.Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
w := httptest.NewRecorder()
|
|
||||||
s.ServeHTTP(w, r)
|
|
||||||
|
|
||||||
return w.Code, w.Body
|
|
||||||
}
|
|
||||||
|
|
||||||
func field(name string, blob json.RawMessage) (v interface{}, ok bool) {
|
|
||||||
var obj map[string]interface{}
|
|
||||||
if err := json.Unmarshal(blob, &obj); err != nil {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
v, ok = obj[name]
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
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.Error("Expected res.Result to be 8, but got", 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)
|
|
||||||
}
|
|
||||||
if code, res := executeRaw(t, s, json.RawMessage(`{"method":"Service1.Multiply","params":null,"id":5}`)); code != 400 {
|
|
||||||
t.Error("Expected response code to be 400, but got", code)
|
|
||||||
} else if v, ok := field("result", res.Bytes()); !ok || v != nil {
|
|
||||||
t.Errorf("Expected ok to be true and v to be nil, but got %v and %v", ok, v)
|
|
||||||
}
|
|
||||||
if err := execute(t, s, "Service1.ResponseJsonError", &Service1Request{4, 2}, &res); err == nil {
|
|
||||||
t.Errorf("Expected to get %q, but got nil", ErrResponseError)
|
|
||||||
} else if jsonErr, ok := err.(*Error); !ok {
|
|
||||||
t.Error("Expected err to be of a *json.Error type")
|
|
||||||
} else if !reflect.DeepEqual(jsonErr.Data, ErrResponseJsonError.Data) {
|
|
||||||
t.Errorf("Expected jsonErr to be %q, but got %q", ErrResponseJsonError, jsonErr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestClientNullResult(t *testing.T) {
|
|
||||||
data := `{"jsonrpc": "2.0", "id": 8674665223082153551, "result": null}`
|
|
||||||
reader := bytes.NewReader([]byte(data))
|
|
||||||
|
|
||||||
var reply interface{}
|
|
||||||
|
|
||||||
err := DecodeClientResponse(reader, &reply)
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if err.Error() != "Unexpected null result" {
|
|
||||||
t.Fatalf("Unexpected error: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,13 +1,12 @@
|
|||||||
// Copyright 2009 The Go Authors. All rights reserved.
|
// Copyright 2009 The Go Authors. All rights reserved.
|
||||||
// Copyright 2012-2013 The Gorilla Authors. All rights reserved.
|
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
package json
|
package json2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
)
|
)
|
||||||
@ -18,10 +17,15 @@ import (
|
|||||||
|
|
||||||
// clientRequest represents a JSON-RPC request sent by a client.
|
// clientRequest represents a JSON-RPC request sent by a client.
|
||||||
type clientRequest struct {
|
type clientRequest struct {
|
||||||
|
// JSON-RPC protocol.
|
||||||
|
Version string `json:"jsonrpc"`
|
||||||
|
|
||||||
// A String containing the name of the method to be invoked.
|
// A String containing the name of the method to be invoked.
|
||||||
Method string `json:"method"`
|
Method string `json:"method"`
|
||||||
|
|
||||||
// Object to pass as request parameter to the method.
|
// Object to pass as request parameter to the method.
|
||||||
Params [1]interface{} `json:"params"`
|
Params interface{} `json:"params"`
|
||||||
|
|
||||||
// The request id. This can be of any type. It is used to match the
|
// The request id. This can be of any type. It is used to match the
|
||||||
// response with the request that it is replying to.
|
// response with the request that it is replying to.
|
||||||
Id uint64 `json:"id"`
|
Id uint64 `json:"id"`
|
||||||
@ -29,16 +33,17 @@ type clientRequest struct {
|
|||||||
|
|
||||||
// clientResponse represents a JSON-RPC response returned to a client.
|
// clientResponse represents a JSON-RPC response returned to a client.
|
||||||
type clientResponse struct {
|
type clientResponse struct {
|
||||||
|
Version string `json:"jsonrpc"`
|
||||||
Result *json.RawMessage `json:"result"`
|
Result *json.RawMessage `json:"result"`
|
||||||
Error interface{} `json:"error"`
|
Error *json.RawMessage `json:"error"`
|
||||||
Id uint64 `json:"id"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// EncodeClientRequest encodes parameters for a JSON-RPC client request.
|
// EncodeClientRequest encodes parameters for a JSON-RPC client request.
|
||||||
func EncodeClientRequest(method string, args interface{}) ([]byte, error) {
|
func EncodeClientRequest(method string, args interface{}) ([]byte, error) {
|
||||||
c := &clientRequest{
|
c := &clientRequest{
|
||||||
|
Version: "2.0",
|
||||||
Method: method,
|
Method: method,
|
||||||
Params: [1]interface{}{args},
|
Params: args,
|
||||||
Id: uint64(rand.Int63()),
|
Id: uint64(rand.Int63()),
|
||||||
}
|
}
|
||||||
return json.Marshal(c)
|
return json.Marshal(c)
|
||||||
@ -52,10 +57,19 @@ func DecodeClientResponse(r io.Reader, reply interface{}) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if c.Error != nil {
|
if c.Error != nil {
|
||||||
return &Error{Data: c.Error}
|
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 {
|
if c.Result == nil {
|
||||||
return fmt.Errorf("Unexpected null result")
|
return ErrNullResult
|
||||||
}
|
}
|
||||||
|
|
||||||
return json.Unmarshal(*c.Result, reply)
|
return json.Unmarshal(*c.Result, reply)
|
||||||
}
|
}
|
39
vendor/github.com/gorilla/rpc/v2/json2/error.go
generated
vendored
Normal file
39
vendor/github.com/gorilla/rpc/v2/json2/error.go
generated
vendored
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
161
vendor/github.com/gorilla/rpc/v2/json2/json_test.go
generated
vendored
Normal file
161
vendor/github.com/gorilla/rpc/v2/json2/json_test.go
generated
vendored
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
package json2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gorilla/rpc/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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")
|
||||||
|
|
||||||
|
type Service1Request struct {
|
||||||
|
A int
|
||||||
|
B int
|
||||||
|
}
|
||||||
|
|
||||||
|
type Service1BadRequest struct {
|
||||||
|
V string `json:"jsonrpc"`
|
||||||
|
M string `json:"method"`
|
||||||
|
ID uint64 `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Service1Response struct {
|
||||||
|
Result int
|
||||||
|
}
|
||||||
|
|
||||||
|
type Service1 struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Service1) Multiply(r *http.Request, req *Service1Request, res *Service1Response) error {
|
||||||
|
res.Result = req.A * req.B
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Service1) ResponseError(r *http.Request, req *Service1Request, res *Service1Response) error {
|
||||||
|
return ErrResponseError
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := executeRaw(t, s, &Service1BadRequest{"2.0", "Service1.Multiply", 1}, &res); err == nil {
|
||||||
|
t.Errorf("Expected error but error in nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
@ -3,29 +3,17 @@
|
|||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
package json
|
package json2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gorilla/rpc/v2"
|
"github.com/gorilla/rpc/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var null = json.RawMessage([]byte("null"))
|
var null = json.RawMessage([]byte("null"))
|
||||||
|
var Version = "2.0"
|
||||||
// An Error is a wrapper for a JSON interface value. It can be used by either
|
|
||||||
// a service's handler func to write more complex JSON data to an error field
|
|
||||||
// of a server's response, or by a client to read it.
|
|
||||||
type Error struct {
|
|
||||||
Data interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Error) Error() string {
|
|
||||||
return fmt.Sprintf("%v", e.Data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
// Request and Response
|
// Request and Response
|
||||||
@ -33,23 +21,36 @@ func (e *Error) Error() string {
|
|||||||
|
|
||||||
// serverRequest represents a JSON-RPC request received by the server.
|
// serverRequest represents a JSON-RPC request received by the server.
|
||||||
type serverRequest struct {
|
type serverRequest struct {
|
||||||
|
// JSON-RPC protocol.
|
||||||
|
Version string `json:"jsonrpc"`
|
||||||
|
|
||||||
// A String containing the name of the method to be invoked.
|
// A String containing the name of the method to be invoked.
|
||||||
Method string `json:"method"`
|
Method string `json:"method"`
|
||||||
// An Array of objects to pass as arguments to the method.
|
|
||||||
|
// A Structured value to pass as arguments to the method.
|
||||||
Params *json.RawMessage `json:"params"`
|
Params *json.RawMessage `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.
|
// 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"`
|
Id *json.RawMessage `json:"id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// serverResponse represents a JSON-RPC response returned by the server.
|
// serverResponse represents a JSON-RPC response returned by the server.
|
||||||
type serverResponse struct {
|
type serverResponse struct {
|
||||||
|
// JSON-RPC protocol.
|
||||||
|
Version string `json:"jsonrpc"`
|
||||||
|
|
||||||
// The Object that was returned by the invoked method. This must be null
|
// The Object that was returned by the invoked method. This must be null
|
||||||
// in case there was an error invoking the method.
|
// in case there was an error invoking the method.
|
||||||
Result interface{} `json:"result"`
|
// 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
|
// An Error object if there was an error invoking the method. It must be
|
||||||
// null if there was no error.
|
// null if there was no error.
|
||||||
Error interface{} `json:"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.
|
// This must be the same id as the request it is responding to.
|
||||||
Id *json.RawMessage `json:"id"`
|
Id *json.RawMessage `json:"id"`
|
||||||
}
|
}
|
||||||
@ -58,18 +59,24 @@ type serverResponse struct {
|
|||||||
// Codec
|
// Codec
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// NewcustomCodec returns a new JSON Codec based on passed encoder selector.
|
||||||
|
func NewCustomCodec(encSel rpc.EncoderSelector) *Codec {
|
||||||
|
return &Codec{encSel: encSel}
|
||||||
|
}
|
||||||
|
|
||||||
// NewCodec returns a new JSON Codec.
|
// NewCodec returns a new JSON Codec.
|
||||||
func NewCodec() *Codec {
|
func NewCodec() *Codec {
|
||||||
return &Codec{}
|
return NewCustomCodec(rpc.DefaultEncoderSelector)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Codec creates a CodecRequest to process each request.
|
// Codec creates a CodecRequest to process each request.
|
||||||
type Codec struct {
|
type Codec struct {
|
||||||
|
encSel rpc.EncoderSelector
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRequest returns a CodecRequest.
|
// NewRequest returns a CodecRequest.
|
||||||
func (c *Codec) NewRequest(r *http.Request) rpc.CodecRequest {
|
func (c *Codec) NewRequest(r *http.Request) rpc.CodecRequest {
|
||||||
return newCodecRequest(r)
|
return newCodecRequest(r, c.encSel.Select(r))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
@ -77,18 +84,33 @@ func (c *Codec) NewRequest(r *http.Request) rpc.CodecRequest {
|
|||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
// newCodecRequest returns a new CodecRequest.
|
// newCodecRequest returns a new CodecRequest.
|
||||||
func newCodecRequest(r *http.Request) rpc.CodecRequest {
|
func newCodecRequest(r *http.Request, encoder rpc.Encoder) rpc.CodecRequest {
|
||||||
// Decode the request body and check if RPC method is valid.
|
// Decode the request body and check if RPC method is valid.
|
||||||
req := new(serverRequest)
|
req := new(serverRequest)
|
||||||
err := json.NewDecoder(r.Body).Decode(req)
|
err := json.NewDecoder(r.Body).Decode(req)
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
r.Body.Close()
|
r.Body.Close()
|
||||||
return &CodecRequest{request: req, err: err}
|
return &CodecRequest{request: req, err: err, encoder: encoder}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CodecRequest decodes and encodes a single request.
|
// CodecRequest decodes and encodes a single request.
|
||||||
type CodecRequest struct {
|
type CodecRequest struct {
|
||||||
request *serverRequest
|
request *serverRequest
|
||||||
err error
|
err error
|
||||||
|
encoder rpc.Encoder
|
||||||
}
|
}
|
||||||
|
|
||||||
// Method returns the RPC method for the current request.
|
// Method returns the RPC method for the current request.
|
||||||
@ -101,16 +123,24 @@ func (c *CodecRequest) Method() (string, error) {
|
|||||||
return "", c.err
|
return "", c.err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadRequest fills the request object for the RPC method.
|
// ReadRe<quest fills the request object for the RPC method.
|
||||||
func (c *CodecRequest) ReadRequest(args interface{}) error {
|
func (c *CodecRequest) ReadRequest(args interface{}) error {
|
||||||
if c.err == nil {
|
if c.err == nil {
|
||||||
if c.request.Params != nil {
|
if c.request.Params != nil {
|
||||||
// JSON params is array value. RPC params is struct.
|
// JSON params structured object. Unmarshal to the args object.
|
||||||
// Unmarshal into array containing the request struct.
|
err := json.Unmarshal(*c.request.Params, args)
|
||||||
params := [1]interface{}{args}
|
if err != nil {
|
||||||
c.err = json.Unmarshal(*c.request.Params, ¶ms)
|
c.err = &Error{
|
||||||
|
Code: E_INVALID_REQ,
|
||||||
|
Message: err.Error(),
|
||||||
|
Data: c.request.Params,
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
c.err = errors.New("rpc: method request ill-formed: missing params field")
|
c.err = &Error{
|
||||||
|
Code: E_INVALID_REQ,
|
||||||
|
Message: "rpc: method request ill-formed: missing params field",
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return c.err
|
return c.err
|
||||||
@ -118,38 +148,43 @@ func (c *CodecRequest) ReadRequest(args interface{}) error {
|
|||||||
|
|
||||||
// WriteResponse encodes the response and writes it to the ResponseWriter.
|
// WriteResponse encodes the response and writes it to the ResponseWriter.
|
||||||
func (c *CodecRequest) WriteResponse(w http.ResponseWriter, reply interface{}) {
|
func (c *CodecRequest) WriteResponse(w http.ResponseWriter, reply interface{}) {
|
||||||
if c.request.Id != nil {
|
|
||||||
// Id is null for notifications and they don't have a response.
|
|
||||||
res := &serverResponse{
|
res := &serverResponse{
|
||||||
|
Version: Version,
|
||||||
Result: reply,
|
Result: reply,
|
||||||
Error: &null,
|
|
||||||
Id: c.request.Id,
|
Id: c.request.Id,
|
||||||
}
|
}
|
||||||
c.writeServerResponse(w, 200, res)
|
c.writeServerResponse(w, res)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CodecRequest) WriteError(w http.ResponseWriter, _ int, err error) {
|
func (c *CodecRequest) WriteError(w http.ResponseWriter, status int, err error) {
|
||||||
|
jsonErr, ok := err.(*Error)
|
||||||
|
if !ok {
|
||||||
|
jsonErr = &Error{
|
||||||
|
Code: E_SERVER,
|
||||||
|
Message: err.Error(),
|
||||||
|
}
|
||||||
|
}
|
||||||
res := &serverResponse{
|
res := &serverResponse{
|
||||||
Result: &null,
|
Version: Version,
|
||||||
|
Error: jsonErr,
|
||||||
Id: c.request.Id,
|
Id: c.request.Id,
|
||||||
}
|
}
|
||||||
if jsonErr, ok := err.(*Error); ok {
|
c.writeServerResponse(w, res)
|
||||||
res.Error = jsonErr.Data
|
|
||||||
} else {
|
|
||||||
res.Error = err.Error()
|
|
||||||
}
|
|
||||||
c.writeServerResponse(w, 400, res)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CodecRequest) writeServerResponse(w http.ResponseWriter, status int, res *serverResponse) {
|
func (c *CodecRequest) writeServerResponse(w http.ResponseWriter, res *serverResponse) {
|
||||||
b, err := json.Marshal(res)
|
// Id is null for notifications and they don't have a response.
|
||||||
if err == nil {
|
if c.request.Id != nil {
|
||||||
w.WriteHeader(status)
|
|
||||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
w.Write(b)
|
encoder := json.NewEncoder(c.encoder.Encode(w))
|
||||||
} else {
|
err := encoder.Encode(res)
|
||||||
|
|
||||||
// Not sure in which case will this happen. But seems harmless.
|
// Not sure in which case will this happen. But seems harmless.
|
||||||
|
if err != nil {
|
||||||
rpc.WriteError(w, 400, err.Error())
|
rpc.WriteError(w, 400, err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type EmptyResponse struct {
|
||||||
|
}
|
6
vendor/vendor.json
vendored
6
vendor/vendor.json
vendored
@ -43,9 +43,9 @@
|
|||||||
"revisionTime": "2015-11-05T07:45:51+08:00"
|
"revisionTime": "2015-11-05T07:45:51+08:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "github.com/gorilla/rpc/v2/json",
|
"path": "github.com/gorilla/rpc/v2/json2",
|
||||||
"revision": "64e20900b8aa38bb0771dec71ba3bcc2b07fc8ec",
|
"revision": "839c4c31c6bb12c15f41802cf89325aa89439ac3",
|
||||||
"revisionTime": "2015-11-05T07:45:51+08:00"
|
"revisionTime": "2015-07-14T15:53:15-07:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "github.com/mattn/go-isatty",
|
"path": "github.com/mattn/go-isatty",
|
||||||
|
Loading…
Reference in New Issue
Block a user