From 6ad39cb3862d843c4a200ba3bbba9e5ba5eda040 Mon Sep 17 00:00:00 2001 From: Krishna Srinivas Date: Thu, 11 Feb 2016 23:41:43 +0530 Subject: [PATCH] WebUI: move from rpc/v2/json to rpc/v2/json2 which has better error response structure. --- routers.go | 4 +- vendor/github.com/gorilla/rpc/v2/json/doc.go | 58 ------- .../gorilla/rpc/v2/json/json_test.go | 132 -------------- .../gorilla/rpc/v2/{json => json2}/client.go | 38 +++-- .../github.com/gorilla/rpc/v2/json2/error.go | 39 +++++ .../gorilla/rpc/v2/json2/json_test.go | 161 ++++++++++++++++++ .../gorilla/rpc/v2/{json => json2}/server.go | 147 ++++++++++------ vendor/vendor.json | 6 +- 8 files changed, 322 insertions(+), 263 deletions(-) delete mode 100644 vendor/github.com/gorilla/rpc/v2/json/doc.go delete mode 100644 vendor/github.com/gorilla/rpc/v2/json/json_test.go rename vendor/github.com/gorilla/rpc/v2/{json => json2}/client.go (71%) create mode 100644 vendor/github.com/gorilla/rpc/v2/json2/error.go create mode 100644 vendor/github.com/gorilla/rpc/v2/json2/json_test.go rename vendor/github.com/gorilla/rpc/v2/{json => json2}/server.go (55%) diff --git a/routers.go b/routers.go index 8ffb86c6f..ab4b1d58b 100644 --- a/routers.go +++ b/routers.go @@ -22,7 +22,7 @@ import ( router "github.com/gorilla/mux" 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/pkg/fs" "github.com/minio/minio/pkg/probe" @@ -65,7 +65,7 @@ func getWebAPIHandler(web *WebAPI) http.Handler { } s := jsonrpc.NewServer() - codec := json.NewCodec() + codec := json2.NewCodec() s.RegisterCodec(codec, "application/json") s.RegisterCodec(codec, "application/json; charset=UTF-8") s.RegisterService(web, "Web") diff --git a/vendor/github.com/gorilla/rpc/v2/json/doc.go b/vendor/github.com/gorilla/rpc/v2/json/doc.go deleted file mode 100644 index 3f92b9cb3..000000000 --- a/vendor/github.com/gorilla/rpc/v2/json/doc.go +++ /dev/null @@ -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 diff --git a/vendor/github.com/gorilla/rpc/v2/json/json_test.go b/vendor/github.com/gorilla/rpc/v2/json/json_test.go deleted file mode 100644 index 50017db74..000000000 --- a/vendor/github.com/gorilla/rpc/v2/json/json_test.go +++ /dev/null @@ -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) - } -} diff --git a/vendor/github.com/gorilla/rpc/v2/json/client.go b/vendor/github.com/gorilla/rpc/v2/json2/client.go similarity index 71% rename from vendor/github.com/gorilla/rpc/v2/json/client.go rename to vendor/github.com/gorilla/rpc/v2/json2/client.go index be05788e2..8f4c1f5ee 100644 --- a/vendor/github.com/gorilla/rpc/v2/json/client.go +++ b/vendor/github.com/gorilla/rpc/v2/json2/client.go @@ -1,13 +1,12 @@ // 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 // license that can be found in the LICENSE file. -package json +package json2 import ( "encoding/json" - "fmt" "io" "math/rand" ) @@ -18,10 +17,15 @@ import ( // 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 [1]interface{} `json:"params"` + 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"` @@ -29,17 +33,18 @@ type clientRequest struct { // clientResponse represents a JSON-RPC response returned to a client. type clientResponse struct { - Result *json.RawMessage `json:"result"` - Error interface{} `json:"error"` - Id uint64 `json:"id"` + Version string `json:"jsonrpc"` + Result *json.RawMessage `json:"result"` + Error *json.RawMessage `json:"error"` } // EncodeClientRequest encodes parameters for a JSON-RPC client request. func EncodeClientRequest(method string, args interface{}) ([]byte, error) { c := &clientRequest{ - Method: method, - Params: [1]interface{}{args}, - Id: uint64(rand.Int63()), + Version: "2.0", + Method: method, + Params: args, + Id: uint64(rand.Int63()), } return json.Marshal(c) } @@ -52,10 +57,19 @@ func DecodeClientResponse(r io.Reader, reply interface{}) error { return err } 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 { - return fmt.Errorf("Unexpected null result") + return ErrNullResult } + return json.Unmarshal(*c.Result, reply) } diff --git a/vendor/github.com/gorilla/rpc/v2/json2/error.go b/vendor/github.com/gorilla/rpc/v2/json2/error.go new file mode 100644 index 000000000..9d2cbd9ee --- /dev/null +++ b/vendor/github.com/gorilla/rpc/v2/json2/error.go @@ -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 +} diff --git a/vendor/github.com/gorilla/rpc/v2/json2/json_test.go b/vendor/github.com/gorilla/rpc/v2/json2/json_test.go new file mode 100644 index 000000000..f3ed2c64b --- /dev/null +++ b/vendor/github.com/gorilla/rpc/v2/json2/json_test.go @@ -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) + } +} diff --git a/vendor/github.com/gorilla/rpc/v2/json/server.go b/vendor/github.com/gorilla/rpc/v2/json2/server.go similarity index 55% rename from vendor/github.com/gorilla/rpc/v2/json/server.go rename to vendor/github.com/gorilla/rpc/v2/json2/server.go index 8fafbe3ad..f04f44a18 100644 --- a/vendor/github.com/gorilla/rpc/v2/json/server.go +++ b/vendor/github.com/gorilla/rpc/v2/json2/server.go @@ -3,29 +3,17 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package json +package json2 import ( "encoding/json" - "errors" - "fmt" "net/http" "github.com/gorilla/rpc/v2" ) var null = json.RawMessage([]byte("null")) - -// 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) -} +var Version = "2.0" // ---------------------------------------------------------------------------- // Request and Response @@ -33,23 +21,36 @@ func (e *Error) Error() string { // 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"` - // 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"` - // 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"` } // 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. - 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 // 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. Id *json.RawMessage `json:"id"` } @@ -58,18 +59,24 @@ type serverResponse struct { // 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. func NewCodec() *Codec { - return &Codec{} + return NewCustomCodec(rpc.DefaultEncoderSelector) } // Codec creates a CodecRequest to process each request. type Codec struct { + encSel rpc.EncoderSelector } // NewRequest returns a 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. -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. req := new(serverRequest) 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() - return &CodecRequest{request: req, err: err} + return &CodecRequest{request: req, err: err, encoder: encoder} } // CodecRequest decodes and encodes a single request. type CodecRequest struct { request *serverRequest err error + encoder rpc.Encoder } // Method returns the RPC method for the current request. @@ -101,16 +123,24 @@ func (c *CodecRequest) Method() (string, error) { return "", c.err } -// ReadRequest fills the request object for the RPC method. +// ReadRe