Skip to content

Commit

Permalink
rpc: add HTTPError type for HTTP error responses (ethereum#22677)
Browse files Browse the repository at this point in the history
The new error type is returned by client operations contains details of
the response error code and response body.

Co-authored-by: Felix Lange <fjl@twurst.com>
  • Loading branch information
ryanc414 and fjl authored Apr 21, 2021
1 parent 67da83a commit 9357280
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 23 deletions.
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 @@ -134,19 +134,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 @@ -194,7 +186,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 @@ -123,3 +123,42 @@ func TestHTTPRespBodyUnlimited(t *testing.T) {
t.Fatalf("response has wrong length %d, want %d", len(r), respLength)
}
}

// 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

0 comments on commit 9357280

Please sign in to comment.