package jsonrpc2 import ( "bytes" "context" "encoding/json" "errors" "fmt" "isle/toolkit" "net/http" ) // HTTPClient makes JSONRPC2 requests over an HTTP endpoint. // // Close should be called once the HTTPClient is not longer needed. type HTTPClient struct { c toolkit.HTTPClient url string } // NewHTTPClient returns an HTTPClient which will use HTTP POST requests against // the given URL as a transport for JSONRPC2 method calls. func NewHTTPClient(httpClient toolkit.HTTPClient, urlStr string) *HTTPClient { return &HTTPClient{ c: httpClient, url: urlStr, } } func (c *HTTPClient) Call( ctx context.Context, rcv any, method string, args ...any, ) error { var ( body = new(bytes.Buffer) enc = json.NewEncoder(body) ) id, err := encodeRequest(ctx, enc, method, args) if err != nil { return fmt.Errorf("encoding request: %w", err) } req, err := http.NewRequestWithContext(ctx, "POST", c.url, body) if err != nil { return fmt.Errorf("constructing POST request to %q: %w", c.url, err) } res, err := c.c.Do(req) if err != nil { return fmt.Errorf("performing request: %w", err) } defer res.Body.Close() dec := json.NewDecoder(res.Body) resID, err := decodeResponse(dec, rcv) if errors.As(err, new(Error)) { return err } else if err != nil { return fmt.Errorf("decoding response: %w", err) } else if resID != id { return fmt.Errorf( "expected response with ID %q, got %q", id, resID, ) } return nil } func (c *HTTPClient) Close() error { return c.c.Close() }