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