rpc: add HTTPError type for HTTP error responses (#22677)

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>
pull/22630/head
ryanc414 4 years ago committed by GitHub
parent 67da83aca5
commit 9357280fce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 29
      rpc/errors.go
  2. 24
      rpc/http.go
  3. 39
      rpc/http_test.go
  4. 12
      rpc/types.go

@ -18,6 +18,35 @@ package rpc
import "fmt" 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 ( var (
_ Error = new(methodNotFoundError) _ Error = new(methodNotFoundError)
_ Error = new(subscriptionNotFoundError) _ Error = new(subscriptionNotFoundError)

@ -134,19 +134,11 @@ func DialHTTP(endpoint string) (*Client, error) {
func (c *Client) sendHTTP(ctx context.Context, op *requestOp, msg interface{}) error { func (c *Client) sendHTTP(ctx context.Context, op *requestOp, msg interface{}) error {
hc := c.writeConn.(*httpConn) hc := c.writeConn.(*httpConn)
respBody, err := hc.doRequest(ctx, msg) respBody, err := hc.doRequest(ctx, msg)
if respBody != nil {
defer respBody.Close()
}
if err != nil { 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 return err
} }
defer respBody.Close()
var respmsg jsonrpcMessage var respmsg jsonrpcMessage
if err := json.NewDecoder(respBody).Decode(&respmsg); err != nil { if err := json.NewDecoder(respBody).Decode(&respmsg); err != nil {
return err return err
@ -194,7 +186,17 @@ func (hc *httpConn) doRequest(ctx context.Context, msg interface{}) (io.ReadClos
return nil, err return nil, err
} }
if resp.StatusCode < 200 || resp.StatusCode >= 300 { 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 return resp.Body, nil
} }

@ -123,3 +123,42 @@ func TestHTTPRespBodyUnlimited(t *testing.T) {
t.Fatalf("response has wrong length %d, want %d", len(r), respLength) 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)
}
}

@ -35,18 +35,6 @@ type API struct {
Public bool // indication if the methods must be considered safe for public use 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 // 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 // a RPC session. Implementations must be go-routine safe since the codec can be called in
// multiple go-routines concurrently. // multiple go-routines concurrently.

Loading…
Cancel
Save