Skip to content

Commit 9357280

Browse files
ryanc414fjl
ryanc414
andauthored
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>
1 parent 67da83a commit 9357280

File tree

4 files changed

+81
-23
lines changed

4 files changed

+81
-23
lines changed

rpc/errors.go

+29
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,35 @@ package rpc
1818

1919
import "fmt"
2020

21+
// HTTPError is returned by client operations when the HTTP status code of the
22+
// response is not a 2xx status.
23+
type HTTPError struct {
24+
StatusCode int
25+
Status string
26+
Body []byte
27+
}
28+
29+
func (err HTTPError) Error() string {
30+
if len(err.Body) == 0 {
31+
return err.Status
32+
}
33+
return fmt.Sprintf("%v: %s", err.Status, err.Body)
34+
}
35+
36+
// Error wraps RPC errors, which contain an error code in addition to the message.
37+
type Error interface {
38+
Error() string // returns the message
39+
ErrorCode() int // returns the code
40+
}
41+
42+
// A DataError contains some data in addition to the error message.
43+
type DataError interface {
44+
Error() string // returns the message
45+
ErrorData() interface{} // returns the error data
46+
}
47+
48+
// Error types defined below are the built-in JSON-RPC errors.
49+
2150
var (
2251
_ Error = new(methodNotFoundError)
2352
_ Error = new(subscriptionNotFoundError)

rpc/http.go

+13-11
Original file line numberDiff line numberDiff line change
@@ -134,19 +134,11 @@ func DialHTTP(endpoint string) (*Client, error) {
134134
func (c *Client) sendHTTP(ctx context.Context, op *requestOp, msg interface{}) error {
135135
hc := c.writeConn.(*httpConn)
136136
respBody, err := hc.doRequest(ctx, msg)
137-
if respBody != nil {
138-
defer respBody.Close()
139-
}
140-
141137
if err != nil {
142-
if respBody != nil {
143-
buf := new(bytes.Buffer)
144-
if _, err2 := buf.ReadFrom(respBody); err2 == nil {
145-
return fmt.Errorf("%v: %v", err, buf.String())
146-
}
147-
}
148138
return err
149139
}
140+
defer respBody.Close()
141+
150142
var respmsg jsonrpcMessage
151143
if err := json.NewDecoder(respBody).Decode(&respmsg); err != nil {
152144
return err
@@ -194,7 +186,17 @@ func (hc *httpConn) doRequest(ctx context.Context, msg interface{}) (io.ReadClos
194186
return nil, err
195187
}
196188
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
197-
return resp.Body, errors.New(resp.Status)
189+
var buf bytes.Buffer
190+
var body []byte
191+
if _, err := buf.ReadFrom(resp.Body); err == nil {
192+
body = buf.Bytes()
193+
}
194+
195+
return nil, HTTPError{
196+
Status: resp.Status,
197+
StatusCode: resp.StatusCode,
198+
Body: body,
199+
}
198200
}
199201
return resp.Body, nil
200202
}

rpc/http_test.go

+39
Original file line numberDiff line numberDiff line change
@@ -123,3 +123,42 @@ func TestHTTPRespBodyUnlimited(t *testing.T) {
123123
t.Fatalf("response has wrong length %d, want %d", len(r), respLength)
124124
}
125125
}
126+
127+
// Tests that an HTTP error results in an HTTPError instance
128+
// being returned with the expected attributes.
129+
func TestHTTPErrorResponse(t *testing.T) {
130+
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
131+
http.Error(w, "error has occurred!", http.StatusTeapot)
132+
}))
133+
defer ts.Close()
134+
135+
c, err := DialHTTP(ts.URL)
136+
if err != nil {
137+
t.Fatal(err)
138+
}
139+
140+
var r string
141+
err = c.Call(&r, "test_method")
142+
if err == nil {
143+
t.Fatal("error was expected")
144+
}
145+
146+
httpErr, ok := err.(HTTPError)
147+
if !ok {
148+
t.Fatalf("unexpected error type %T", err)
149+
}
150+
151+
if httpErr.StatusCode != http.StatusTeapot {
152+
t.Error("unexpected status code", httpErr.StatusCode)
153+
}
154+
if httpErr.Status != "418 I'm a teapot" {
155+
t.Error("unexpected status text", httpErr.Status)
156+
}
157+
if body := string(httpErr.Body); body != "error has occurred!\n" {
158+
t.Error("unexpected body", body)
159+
}
160+
161+
if errMsg := httpErr.Error(); errMsg != "418 I'm a teapot: error has occurred!\n" {
162+
t.Error("unexpected error message", errMsg)
163+
}
164+
}

rpc/types.go

-12
Original file line numberDiff line numberDiff line change
@@ -35,18 +35,6 @@ type API struct {
3535
Public bool // indication if the methods must be considered safe for public use
3636
}
3737

38-
// Error wraps RPC errors, which contain an error code in addition to the message.
39-
type Error interface {
40-
Error() string // returns the message
41-
ErrorCode() int // returns the code
42-
}
43-
44-
// A DataError contains some data in addition to the error message.
45-
type DataError interface {
46-
Error() string // returns the message
47-
ErrorData() interface{} // returns the error data
48-
}
49-
5038
// ServerCodec implements reading, parsing and writing RPC messages for the server side of
5139
// a RPC session. Implementations must be go-routine safe since the codec can be called in
5240
// multiple go-routines concurrently.

0 commit comments

Comments
 (0)