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