open-nomad/vendor/github.com/renier/xmlrpc/client.go
Seth Hoenig 435c0d9fc8 deps: Switch to Go modules for dependency management
This PR switches the Nomad repository from using govendor to Go modules
for managing dependencies. Aspects of the Nomad workflow remain pretty
much the same. The usual Makefile targets should continue to work as
they always did. The API submodule simply defers to the parent Nomad
version on the repository, keeping the semantics of API versioning that
currently exists.
2020-06-02 14:30:36 -05:00

182 lines
3.7 KiB
Go

package xmlrpc
import (
"fmt"
"io"
"io/ioutil"
"net/http"
"net/http/cookiejar"
"net/rpc"
"net/url"
"strconv"
"sync"
"time"
)
type Client struct {
*rpc.Client
}
// clientCodec is rpc.ClientCodec interface implementation.
type clientCodec struct {
// url presents url of xmlrpc service
url *url.URL
// httpClient works with HTTP protocol
httpClient *http.Client
// cookies stores cookies received on last request
cookies http.CookieJar
// responses presents map of active requests. It is required to return request id, that
// rpc.Client can mark them as done.
responsesMu sync.RWMutex
responses map[uint64]*http.Response
response *Response
// ready presents channel, that is used to link request and it`s response.
ready chan *uint64
}
func (codec *clientCodec) WriteRequest(request *rpc.Request, args interface{}) (err error) {
httpRequest, err := NewRequest(codec.url.String(), request.ServiceMethod, args)
if err != nil {
return err
}
if codec.cookies != nil {
for _, cookie := range codec.cookies.Cookies(codec.url) {
httpRequest.AddCookie(cookie)
}
}
httpResponse, err := codec.httpClient.Do(httpRequest)
if err != nil {
return err
}
if codec.cookies != nil {
codec.cookies.SetCookies(codec.url, httpResponse.Cookies())
}
codec.responsesMu.Lock()
codec.responses[request.Seq] = httpResponse
codec.responsesMu.Unlock()
codec.ready <- &request.Seq
return nil
}
func (codec *clientCodec) ReadResponseHeader(response *rpc.Response) error {
seq, ok := <-codec.ready
if !ok {
return io.EOF
}
codec.responsesMu.RLock()
httpResponse := codec.responses[*seq]
codec.responsesMu.RUnlock()
defer func() {
httpResponse.Body.Close()
codec.responsesMu.Lock()
delete(codec.responses, *seq)
codec.responsesMu.Unlock()
}()
contentLength := httpResponse.ContentLength
if contentLength == -1 {
if ntcoentLengthHeader, ok := httpResponse.Header["Ntcoent-Length"]; ok {
ntcoentLength, err := strconv.ParseInt(ntcoentLengthHeader[0], 10, 64)
if err == nil {
contentLength = ntcoentLength
}
}
}
var respData []byte
var err error
if contentLength != -1 {
respData = make([]byte, contentLength)
_, err = io.ReadFull(httpResponse.Body, respData)
} else {
respData, err = ioutil.ReadAll(httpResponse.Body)
}
if err != nil {
return err
}
resp := NewResponse(respData, httpResponse.StatusCode)
if resp.Failed() {
err := resp.Err()
response.Error = fmt.Sprintf("%v", err)
return err
}
codec.response = resp
response.Seq = *seq
if httpResponse.StatusCode < 200 || httpResponse.StatusCode >= 300 {
return &XmlRpcError{HttpStatusCode: httpResponse.StatusCode}
}
return nil
}
func (codec *clientCodec) ReadResponseBody(v interface{}) (err error) {
if v == nil {
return nil
}
if err = codec.response.Unmarshal(v); err != nil {
return err
}
return nil
}
func (codec *clientCodec) Close() error {
transport := codec.httpClient.Transport.(*http.Transport)
transport.CloseIdleConnections()
close(codec.ready)
return nil
}
// NewClient returns instance of rpc.Client object, that is used to send request to xmlrpc service.
func NewClient(requrl string, transport http.RoundTripper, timeout time.Duration) (*Client, error) {
if transport == nil {
transport = http.DefaultTransport
}
httpClient := &http.Client{
Transport: transport,
Timeout: timeout,
}
jar, err := cookiejar.New(nil)
if err != nil {
return nil, err
}
u, err := url.Parse(requrl)
if err != nil {
return nil, err
}
codec := clientCodec{
url: u,
httpClient: httpClient,
ready: make(chan *uint64),
responses: make(map[uint64]*http.Response),
cookies: jar,
}
return &Client{rpc.NewClientWithCodec(&codec)}, nil
}