1package xmlrpc
2
3import (
4	"fmt"
5	"io/ioutil"
6	"net/http"
7	"net/http/cookiejar"
8	"net/rpc"
9	"net/url"
10)
11
12type Client struct {
13	*rpc.Client
14}
15
16// clientCodec is rpc.ClientCodec interface implementation.
17type clientCodec struct {
18	// url presents url of xmlrpc service
19	url *url.URL
20
21	// httpClient works with HTTP protocol
22	httpClient *http.Client
23
24	// cookies stores cookies received on last request
25	cookies http.CookieJar
26
27	// responses presents map of active requests. It is required to return request id, that
28	// rpc.Client can mark them as done.
29	responses map[uint64]*http.Response
30
31	response *Response
32
33	// ready presents channel, that is used to link request and it`s response.
34	ready chan uint64
35}
36
37func (codec *clientCodec) WriteRequest(request *rpc.Request, args interface{}) (err error) {
38	httpRequest, err := NewRequest(codec.url.String(), request.ServiceMethod, args)
39
40	if codec.cookies != nil {
41		for _, cookie := range codec.cookies.Cookies(codec.url) {
42			httpRequest.AddCookie(cookie)
43		}
44	}
45
46	if err != nil {
47		return err
48	}
49
50	var httpResponse *http.Response
51	httpResponse, err = codec.httpClient.Do(httpRequest)
52
53	if err != nil {
54		return err
55	}
56
57	if codec.cookies != nil {
58		codec.cookies.SetCookies(codec.url, httpResponse.Cookies())
59	}
60
61	codec.responses[request.Seq] = httpResponse
62	codec.ready <- request.Seq
63
64	return nil
65}
66
67func (codec *clientCodec) ReadResponseHeader(response *rpc.Response) (err error) {
68	seq := <-codec.ready
69	httpResponse := codec.responses[seq]
70
71	if httpResponse.StatusCode < 200 || httpResponse.StatusCode >= 300 {
72		return fmt.Errorf("request error: bad status code - %d", httpResponse.StatusCode)
73	}
74
75	respData, err := ioutil.ReadAll(httpResponse.Body)
76
77	if err != nil {
78		return err
79	}
80
81	httpResponse.Body.Close()
82
83	resp := NewResponse(respData)
84
85	if resp.Failed() {
86		response.Error = fmt.Sprintf("%v", resp.Err())
87	}
88
89	codec.response = resp
90
91	response.Seq = seq
92	delete(codec.responses, seq)
93
94	return nil
95}
96
97func (codec *clientCodec) ReadResponseBody(v interface{}) (err error) {
98	if v == nil {
99		return nil
100	}
101
102	if err = codec.response.Unmarshal(v); err != nil {
103		return err
104	}
105
106	return nil
107}
108
109func (codec *clientCodec) Close() error {
110	transport := codec.httpClient.Transport.(*http.Transport)
111	transport.CloseIdleConnections()
112	return nil
113}
114
115// NewClient returns instance of rpc.Client object, that is used to send request to xmlrpc service.
116func NewClient(requrl string, transport http.RoundTripper) (*Client, error) {
117	if transport == nil {
118		transport = http.DefaultTransport
119	}
120
121	httpClient := &http.Client{Transport: transport}
122
123	jar, err := cookiejar.New(nil)
124
125	if err != nil {
126		return nil, err
127	}
128
129	u, err := url.Parse(requrl)
130
131	if err != nil {
132		return nil, err
133	}
134
135	codec := clientCodec{
136		url:        u,
137		httpClient: httpClient,
138		ready:      make(chan uint64),
139		responses:  make(map[uint64]*http.Response),
140		cookies:    jar,
141	}
142
143	return &Client{rpc.NewClientWithCodec(&codec)}, nil
144}
145