From dae975c995596644fa229575456c3fa136a0c737 Mon Sep 17 00:00:00 2001 From: Ryan Collingham Date: Thu, 15 Apr 2021 16:25:47 +0100 Subject: [PATCH 1/3] rpc: Return new HTTPError type for HTTP error responses New error type holds details of the specific error code and response body. --- rpc/http.go | 38 +++++++++++++++++++++++++++----------- rpc/http_test.go | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 11 deletions(-) diff --git a/rpc/http.go b/rpc/http.go index 87a96e49eaf..067ebb1d9fb 100644 --- a/rpc/http.go +++ b/rpc/http.go @@ -131,22 +131,28 @@ func DialHTTP(endpoint string) (*Client, error) { return DialHTTPWithClient(endpoint, new(http.Client)) } +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: %v", err.Status, string(err.Body)) +} + 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 @@ -194,7 +200,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 } diff --git a/rpc/http_test.go b/rpc/http_test.go index b75af67c522..97f8d44c39b 100644 --- a/rpc/http_test.go +++ b/rpc/http_test.go @@ -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) + } +} From 2f3b97fdb9a19a76159175499ce641104dc5a3ee Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 21 Apr 2021 14:53:47 +0200 Subject: [PATCH 2/3] rpc: move HTTPError to error.go --- rpc/errors.go | 17 +++++++++++++++++ rpc/http.go | 14 -------------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/rpc/errors.go b/rpc/errors.go index dbfde8b1965..281b57ff0d3 100644 --- a/rpc/errors.go +++ b/rpc/errors.go @@ -18,6 +18,23 @@ 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 types defined below are the built-in JSON-RPC errors. + var ( _ Error = new(methodNotFoundError) _ Error = new(subscriptionNotFoundError) diff --git a/rpc/http.go b/rpc/http.go index 067ebb1d9fb..32f4e7d90a2 100644 --- a/rpc/http.go +++ b/rpc/http.go @@ -131,20 +131,6 @@ func DialHTTP(endpoint string) (*Client, error) { return DialHTTPWithClient(endpoint, new(http.Client)) } -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: %v", err.Status, string(err.Body)) -} - func (c *Client) sendHTTP(ctx context.Context, op *requestOp, msg interface{}) error { hc := c.writeConn.(*httpConn) respBody, err := hc.doRequest(ctx, msg) From f02ed526d0434a0cafe5a7db978b86aabfb13096 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 21 Apr 2021 14:54:47 +0200 Subject: [PATCH 3/3] rpc: also move Error to errors.go --- rpc/errors.go | 12 ++++++++++++ rpc/types.go | 12 ------------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/rpc/errors.go b/rpc/errors.go index 281b57ff0d3..4c06a745fbd 100644 --- a/rpc/errors.go +++ b/rpc/errors.go @@ -33,6 +33,18 @@ func (err HTTPError) Error() string { 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 ( diff --git a/rpc/types.go b/rpc/types.go index bab1b3957b9..d1b878c7858 100644 --- a/rpc/types.go +++ b/rpc/types.go @@ -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.