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