xmlrpc.go raw
1 /**
2 * Copyright 2016 IBM Corp.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package session
18
19 import (
20 "encoding/json"
21 "fmt"
22 "math/rand"
23 "net/http"
24 "net/http/httputil"
25 "strings"
26 "time"
27
28 "github.com/softlayer/softlayer-go/sl"
29 "github.com/softlayer/xmlrpc"
30 )
31
32 // Debugging RoundTripper
33 type debugRoundTripper struct{}
34
35 func (mrt debugRoundTripper) RoundTrip(request *http.Request) (*http.Response, error) {
36 log := Logger
37 log.Println("->>>Request:")
38 dumpedReq, _ := httputil.DumpRequestOut(request, true)
39 log.Println(string(dumpedReq))
40
41 response, err := http.DefaultTransport.RoundTrip(request)
42 if err != nil {
43 log.Println("Error:", err)
44 return response, err
45 }
46
47 log.Println("\n\n<<<-Response:")
48 dumpedResp, _ := httputil.DumpResponse(response, true)
49 log.Println(string(dumpedResp))
50
51 return response, err
52 }
53
54 // XML-RPC Transport
55 type XmlRpcTransport struct{}
56
57 func (x *XmlRpcTransport) DoRequest(
58 sess *Session,
59 service string,
60 method string,
61 args []interface{},
62 options *sl.Options,
63 pResult interface{},
64 ) error {
65
66 var err error
67 serviceUrl := fmt.Sprintf("%s/%s", strings.TrimRight(sess.Endpoint, "/"), service)
68
69 timeout := DefaultTimeout
70 if sess.Timeout != 0 {
71 timeout = sess.Timeout
72 }
73
74 // Declaring client outside of the if /else. So we can set the correct http transport based if it is TLS or not
75 var client *xmlrpc.Client
76 if sess.HTTPClient != nil && sess.HTTPClient.Transport != nil {
77 client, err = xmlrpc.NewClient(serviceUrl, sess.HTTPClient.Transport, timeout)
78 } else {
79 var roundTripper http.RoundTripper
80 if sess.Debug {
81 roundTripper = debugRoundTripper{}
82 }
83
84 client, err = xmlrpc.NewClient(serviceUrl, roundTripper, timeout)
85 }
86 //Verify no errors happened in creating the xmlrpc client
87 if err != nil {
88 return fmt.Errorf("Could not create an xmlrpc client for %s: %s", service, err)
89 }
90
91 authenticate := map[string]interface{}{}
92 if sess.UserName != "" {
93 authenticate["username"] = sess.UserName
94 }
95
96 if sess.APIKey != "" {
97 authenticate["apiKey"] = sess.APIKey
98 }
99
100 if sess.UserId != 0 {
101 authenticate["userId"] = sess.UserId
102 authenticate["complexType"] = "PortalLoginToken"
103 }
104
105 if sess.AuthToken != "" {
106 authenticate["authToken"] = sess.AuthToken
107 authenticate["complexType"] = "PortalLoginToken"
108 }
109
110 // For cases where session is built from the raw structure and not using New() , the UserAgent would be empty
111 if sess.userAgent == "" {
112 sess.userAgent = getDefaultUserAgent()
113 }
114
115 headers := map[string]interface{}{}
116 headers["User-Agent"] = sess.userAgent
117
118 if len(authenticate) > 0 {
119 headers["authenticate"] = authenticate
120 }
121
122 if options.Id != nil {
123 headers[fmt.Sprintf("%sInitParameters", service)] = map[string]int{
124 "id": *options.Id,
125 }
126 }
127
128 mask := options.Mask
129
130 if mask != "" {
131 if !strings.HasPrefix(mask, "mask[") {
132 mask = fmt.Sprintf("mask[%s]", mask)
133 }
134 headers["SoftLayer_ObjectMask"] = map[string]string{"mask": mask}
135 }
136
137 if options.Filter != "" {
138 // FIXME: This json unmarshaling presents a performance problem,
139 // since the filter builder marshals a data structure to json.
140 // This then undoes that step to pass it to the xmlrpc request.
141 // It would be better to get the umarshaled data structure
142 // from the filter builder, but that will require changes to the
143 // public API in Options.
144 objFilter := map[string]interface{}{}
145 err := json.Unmarshal([]byte(options.Filter), &objFilter)
146 if err != nil {
147 return fmt.Errorf("Error encoding object filter: %s", err)
148 }
149 headers[fmt.Sprintf("%sObjectFilter", service)] = objFilter
150 }
151
152 if options.Limit != nil {
153 offset := 0
154 if options.Offset != nil {
155 offset = *options.Offset
156 }
157
158 headers["resultLimit"] = map[string]int{
159 "limit": *options.Limit,
160 "offset": offset,
161 }
162 }
163
164 // Add incoming arguments to xmlrpc parameter array
165 params := []interface{}{}
166
167 if len(headers) > 0 {
168 params = append(params, map[string]interface{}{"headers": headers})
169 }
170
171 for _, arg := range args {
172 params = append(params, arg)
173 }
174
175 retries := sess.Retries
176 if retries < 2 {
177 err = client.Call(method, params, pResult)
178 } else {
179 wait := sess.RetryWait
180 if wait == 0 {
181 wait = DefaultRetryWait
182 }
183
184 err = makeXmlRequest(retries, wait, client, method, params, pResult)
185 }
186
187 if xmlRpcError, ok := err.(*xmlrpc.XmlRpcError); ok {
188 err = sl.Error{
189 StatusCode: xmlRpcError.HttpStatusCode,
190 Exception: xmlRpcError.Code.(string),
191 Message: xmlRpcError.Err,
192 }
193 }
194 return err
195 }
196
197 func makeXmlRequest(
198 retries int, wait time.Duration, client *xmlrpc.Client,
199 method string, params []interface{}, pResult interface{}) error {
200
201 err := client.Call(method, params, pResult)
202
203 if xmlRpcError, ok := err.(*xmlrpc.XmlRpcError); ok {
204 err = sl.Error{
205 StatusCode: xmlRpcError.HttpStatusCode,
206 Exception: xmlRpcError.Code.(string),
207 Message: xmlRpcError.Err,
208 }
209 }
210
211 if err != nil {
212 if !isRetryable(err) {
213 return err
214 }
215
216 if retries--; retries > 0 {
217 jitter := time.Duration(rand.Int63n(int64(wait)))
218 wait = wait + jitter/2
219 time.Sleep(wait)
220 return makeXmlRequest(
221 retries, wait, client, method, params, pResult)
222 }
223 }
224
225 return err
226 }
227
228 func genXMLMask(mask string) interface{} {
229 objectMask := map[string]interface{}{}
230 for _, item := range strings.Split(mask, ";") {
231 if !strings.Contains(item, ".") {
232 objectMask[item] = []string{}
233 continue
234 }
235
236 level := objectMask
237 names := strings.Split(item, ".")
238 totalNames := len(names)
239 for i, name := range names {
240 if i == totalNames-1 {
241 level[name] = []string{}
242 continue
243 }
244
245 level[name] = map[string]interface{}{}
246 level = level[name].(map[string]interface{})
247 }
248 }
249
250 return objectMask
251 }
252