Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions rpc/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,35 @@ package rpc

import "fmt"

// HTTPError is returned by client operations when the HTTP status code of the
// response is not a 2xx status.
type HTTPError struct {
StatusCode int
Status string
Body []byte
}

func (err HTTPError) Error() string {
if len(err.Body) == 0 {
return err.Status
}
return fmt.Sprintf("%v: %s", err.Status, err.Body)
}

// Error wraps RPC errors, which contain an error code in addition to the message.
type Error interface {
Error() string // returns the message
ErrorCode() int // returns the code
}

// A DataError contains some data in addition to the error message.
type DataError interface {
Error() string // returns the message
ErrorData() interface{} // returns the error data
}

// Error types defined below are the built-in JSON-RPC errors.

var (
_ Error = new(methodNotFoundError)
_ Error = new(subscriptionNotFoundError)
Expand Down
24 changes: 13 additions & 11 deletions rpc/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,19 +140,11 @@ func DialHTTP(endpoint string) (*Client, error) {
func (c *Client) sendHTTP(ctx context.Context, op *requestOp, msg interface{}) error {
hc := c.writeConn.(*httpConn)
respBody, err := hc.doRequest(ctx, msg)
if respBody != nil {
defer respBody.Close()
}

if err != nil {
if respBody != nil {
buf := new(bytes.Buffer)
if _, err2 := buf.ReadFrom(respBody); err2 == nil {
return fmt.Errorf("%v: %v", err, buf.String())
}
}
return err
}
defer respBody.Close()

var respmsg jsonrpcMessage
if err := json.NewDecoder(respBody).Decode(&respmsg); err != nil {
return err
Expand Down Expand Up @@ -203,7 +195,17 @@ func (hc *httpConn) doRequest(ctx context.Context, msg interface{}) (io.ReadClos
return nil, err
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return resp.Body, errors.New(resp.Status)
var buf bytes.Buffer
var body []byte
if _, err := buf.ReadFrom(resp.Body); err == nil {
body = buf.Bytes()
}

return nil, HTTPError{
Status: resp.Status,
StatusCode: resp.StatusCode,
Body: body,
}
}
return resp.Body, nil
}
Expand Down
39 changes: 39 additions & 0 deletions rpc/http_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,3 +160,42 @@ func TestHTTPPeerInfo(t *testing.T) {
t.Errorf("wrong HTTP.Origin %q", info.HTTP.UserAgent)
}
}

// Tests that an HTTP error results in an HTTPError instance
// being returned with the expected attributes.
func TestHTTPErrorResponse(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Error(w, "error has occurred!", http.StatusTeapot)
}))
defer ts.Close()

c, err := DialHTTP(ts.URL)
if err != nil {
t.Fatal(err)
}

var r string
err = c.Call(&r, "test_method")
if err == nil {
t.Fatal("error was expected")
}

httpErr, ok := err.(HTTPError)
if !ok {
t.Fatalf("unexpected error type %T", err)
}

if httpErr.StatusCode != http.StatusTeapot {
t.Error("unexpected status code", httpErr.StatusCode)
}
if httpErr.Status != "418 I'm a teapot" {
t.Error("unexpected status text", httpErr.Status)
}
if body := string(httpErr.Body); body != "error has occurred!\n" {
t.Error("unexpected body", body)
}

if errMsg := httpErr.Error(); errMsg != "418 I'm a teapot: error has occurred!\n" {
t.Error("unexpected error message", errMsg)
}
}
12 changes: 0 additions & 12 deletions rpc/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,18 +35,6 @@ type API struct {
Public bool // indication if the methods must be considered safe for public use
}

// Error wraps RPC errors, which contain an error code in addition to the message.
type Error interface {
Error() string // returns the message
ErrorCode() int // returns the code
}

// A DataError contains some data in addition to the error message.
type DataError interface {
Error() string // returns the message
ErrorData() interface{} // returns the error data
}

// ServerCodec implements reading, parsing and writing RPC messages for the server side of
// a RPC session. Implementations must be go-routine safe since the codec can be called in
// multiple go-routines concurrently.
Expand Down