/
client.go
117 lines (99 loc) · 3.1 KB
/
client.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
package xmlrpc
import (
"bytes"
"encoding/xml"
"fmt"
"io"
"net/http"
"github.com/mdzio/go-logging"
"golang.org/x/net/html/charset"
"golang.org/x/text/encoding/charmap"
)
// max. size of a valid response, if not specified: 10 MB
const responseSizeLimit = 10 * 1024 * 1024
// Caller is an interface for calling XML-RPC functions.
type Caller interface {
Call(method string, params Values) (*Value, error)
}
var clnLog = logging.Get("xmlrpc-client")
// Client provides access to an XML-RPC server.
type Client struct {
Addr string
ResponseSizeLimit int64
}
// Call executes an remote procedure call. Call implements Caller.
func (c *Client) Call(method string, params Values) (*Value, error) {
clnLog.Tracef("Calling method %s on %s", method, c.Addr)
// build XML object tree
ps := make([]*Param, len(params))
for i, p := range params {
ps[i] = &Param{p}
}
methodCall := &MethodCall{
MethodName: method,
Params: &Params{ps},
}
// use ISO8859-1 character encoding for request
var reqBuf bytes.Buffer
reqWriter := charmap.ISO8859_1.NewEncoder().Writer(&reqBuf)
// write xml header
reqWriter.Write([]byte("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n"))
// encode request to xml
enc := xml.NewEncoder(reqWriter)
err := enc.Encode(methodCall)
if err != nil {
return nil, fmt.Errorf("Encoding of request for %s failed: %v", c.Addr, err)
}
if clnLog.TraceEnabled() {
// attention: log message is ISO8859-1 encoded!
clnLog.Tracef("Request XML: %s", reqBuf.String())
}
// http post
httpResp, err := http.Post("http://"+c.Addr, "text/xml", bytes.NewReader(reqBuf.Bytes()))
if err != nil {
return nil, fmt.Errorf("HTTP request failed on %s: %v", c.Addr, err)
}
defer httpResp.Body.Close()
// check status
if httpResp.StatusCode < 200 || httpResp.StatusCode >= 299 {
return nil, fmt.Errorf("HTTP request failed on %s with code: %s", c.Addr, httpResp.Status)
}
// read response
limit := c.ResponseSizeLimit
if limit == 0 {
limit = responseSizeLimit
}
limitReader := io.LimitReader(httpResp.Body, limit)
respBuf, err := io.ReadAll(limitReader)
if err != nil {
return nil, fmt.Errorf("Reading of response failed from %s: %v", c.Addr, err)
}
if clnLog.TraceEnabled() {
// attention: log message is probably ISO8859-1 encoded!
clnLog.Tracef("Response XML: %s", string(respBuf))
}
// decode response from xml
respReader := bytes.NewBuffer(respBuf)
resp := &MethodResponse{}
dec := xml.NewDecoder(respReader)
dec.CharsetReader = charset.NewReaderLabel
err = dec.Decode(resp)
if err != nil {
return nil, fmt.Errorf("Decoding of response from %s failed: %v", c.Addr, err)
}
// check fault
if resp.Fault != nil {
e := Q(resp.Fault)
faultCode := e.Key("faultCode").Int()
faultString := e.Key("faultString").String()
if e.Err() != nil {
return nil, fmt.Errorf("Invalid XML-RPC fault response: %v", e.Err())
}
return nil, &MethodError{faultCode, faultString}
}
// check response
if resp.Params == nil || len(resp.Params.Param) != 1 {
return nil, fmt.Errorf("Invalid or no parameters in response from %s", c.Addr)
}
return resp.Params.Param[0].Value, nil
}