forked from cerc-io/plugeth
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>
This commit is contained in:
parent
67da83aca5
commit
9357280fce
@ -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)
|
||||||
|
24
rpc/http.go
24
rpc/http.go
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
12
rpc/types.go
12
rpc/types.go
@ -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…
Reference in New Issue
Block a user