254 lines
6.2 KiB
Go
254 lines
6.2 KiB
Go
/**
|
|
* Copyright 2016 IBM Corp.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package session
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"math/rand"
|
|
"net/http"
|
|
"net/http/httputil"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/renier/xmlrpc"
|
|
"github.com/softlayer/softlayer-go/sl"
|
|
)
|
|
|
|
// Debugging RoundTripper
|
|
type debugRoundTripper struct{}
|
|
|
|
func (mrt debugRoundTripper) RoundTrip(request *http.Request) (*http.Response, error) {
|
|
log := Logger
|
|
log.Println("->>>Request:")
|
|
dumpedReq, _ := httputil.DumpRequestOut(request, true)
|
|
log.Println(string(dumpedReq))
|
|
|
|
response, err := http.DefaultTransport.RoundTrip(request)
|
|
if err != nil {
|
|
log.Println("Error:", err)
|
|
return response, err
|
|
}
|
|
|
|
log.Println("\n\n<<<-Response:")
|
|
dumpedResp, _ := httputil.DumpResponse(response, true)
|
|
log.Println(string(dumpedResp))
|
|
|
|
return response, err
|
|
}
|
|
|
|
// XML-RPC Transport
|
|
type XmlRpcTransport struct{}
|
|
|
|
func (x *XmlRpcTransport) DoRequest(
|
|
sess *Session,
|
|
service string,
|
|
method string,
|
|
args []interface{},
|
|
options *sl.Options,
|
|
pResult interface{},
|
|
) error {
|
|
|
|
var err error
|
|
serviceUrl := fmt.Sprintf("%s/%s", strings.TrimRight(sess.Endpoint, "/"), service)
|
|
|
|
timeout := DefaultTimeout
|
|
if sess.Timeout != 0 {
|
|
timeout = sess.Timeout
|
|
}
|
|
|
|
// Declaring client outside of the if /else. So we can set the correct http transport based if it is TLS or not
|
|
var client *xmlrpc.Client
|
|
if sess.HTTPClient != nil && sess.HTTPClient.Transport != nil {
|
|
client, err = xmlrpc.NewClient(serviceUrl, sess.HTTPClient.Transport, timeout)
|
|
} else {
|
|
var roundTripper http.RoundTripper
|
|
if sess.Debug {
|
|
roundTripper = debugRoundTripper{}
|
|
}
|
|
|
|
client, err = xmlrpc.NewClient(serviceUrl, roundTripper, timeout)
|
|
}
|
|
//Verify no errors happened in creating the xmlrpc client
|
|
if err != nil {
|
|
return fmt.Errorf("Could not create an xmlrpc client for %s: %s", service, err)
|
|
}
|
|
|
|
authenticate := map[string]interface{}{}
|
|
if sess.UserName != "" {
|
|
authenticate["username"] = sess.UserName
|
|
}
|
|
|
|
if sess.APIKey != "" {
|
|
authenticate["apiKey"] = sess.APIKey
|
|
}
|
|
|
|
if sess.UserId != 0 {
|
|
authenticate["userId"] = sess.UserId
|
|
authenticate["complexType"] = "PortalLoginToken"
|
|
}
|
|
|
|
if sess.AuthToken != "" {
|
|
authenticate["authToken"] = sess.AuthToken
|
|
authenticate["complexType"] = "PortalLoginToken"
|
|
}
|
|
|
|
// For cases where session is built from the raw structure and not using New() , the UserAgent would be empty
|
|
if sess.userAgent == "" {
|
|
sess.userAgent = getDefaultUserAgent()
|
|
}
|
|
|
|
headers := map[string]interface{}{}
|
|
headers["User-Agent"] = sess.userAgent
|
|
|
|
if len(authenticate) > 0 {
|
|
headers["authenticate"] = authenticate
|
|
}
|
|
|
|
if options.Id != nil {
|
|
headers[fmt.Sprintf("%sInitParameters", service)] = map[string]int{
|
|
"id": *options.Id,
|
|
}
|
|
}
|
|
|
|
mask := options.Mask
|
|
if mask != "" {
|
|
if !strings.HasPrefix(mask, "mask[") && !strings.Contains(mask, ";") && strings.Contains(mask, ",") {
|
|
mask = fmt.Sprintf("mask[%s]", mask)
|
|
headers["SoftLayer_ObjectMask"] = map[string]string{"mask": mask}
|
|
} else {
|
|
headers[fmt.Sprintf("%sObjectMask", service)] =
|
|
map[string]interface{}{"mask": genXMLMask(mask)}
|
|
}
|
|
}
|
|
|
|
if options.Filter != "" {
|
|
// FIXME: This json unmarshaling presents a performance problem,
|
|
// since the filter builder marshals a data structure to json.
|
|
// This then undoes that step to pass it to the xmlrpc request.
|
|
// It would be better to get the umarshaled data structure
|
|
// from the filter builder, but that will require changes to the
|
|
// public API in Options.
|
|
objFilter := map[string]interface{}{}
|
|
err := json.Unmarshal([]byte(options.Filter), &objFilter)
|
|
if err != nil {
|
|
return fmt.Errorf("Error encoding object filter: %s", err)
|
|
}
|
|
headers[fmt.Sprintf("%sObjectFilter", service)] = objFilter
|
|
}
|
|
|
|
if options.Limit != nil {
|
|
offset := 0
|
|
if options.Offset != nil {
|
|
offset = *options.Offset
|
|
}
|
|
|
|
headers["resultLimit"] = map[string]int{
|
|
"limit": *options.Limit,
|
|
"offset": offset,
|
|
}
|
|
}
|
|
|
|
// Add incoming arguments to xmlrpc parameter array
|
|
params := []interface{}{}
|
|
|
|
if len(headers) > 0 {
|
|
params = append(params, map[string]interface{}{"headers": headers})
|
|
}
|
|
|
|
for _, arg := range args {
|
|
params = append(params, arg)
|
|
}
|
|
|
|
retries := sess.Retries
|
|
if retries < 2 {
|
|
err = client.Call(method, params, pResult)
|
|
} else {
|
|
wait := sess.RetryWait
|
|
if wait == 0 {
|
|
wait = DefaultRetryWait
|
|
}
|
|
|
|
err = makeXmlRequest(retries, wait, client, method, params, pResult)
|
|
}
|
|
|
|
if xmlRpcError, ok := err.(*xmlrpc.XmlRpcError); ok {
|
|
err = sl.Error{
|
|
StatusCode: xmlRpcError.HttpStatusCode,
|
|
Exception: xmlRpcError.Code.(string),
|
|
Message: xmlRpcError.Err,
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
|
|
func makeXmlRequest(
|
|
retries int, wait time.Duration, client *xmlrpc.Client,
|
|
method string, params []interface{}, pResult interface{}) error {
|
|
|
|
err := client.Call(method, params, pResult)
|
|
|
|
if xmlRpcError, ok := err.(*xmlrpc.XmlRpcError); ok {
|
|
err = sl.Error{
|
|
StatusCode: xmlRpcError.HttpStatusCode,
|
|
Exception: xmlRpcError.Code.(string),
|
|
Message: xmlRpcError.Err,
|
|
}
|
|
}
|
|
|
|
if err != nil {
|
|
if !isRetryable(err) {
|
|
return err
|
|
}
|
|
|
|
if retries--; retries > 0 {
|
|
jitter := time.Duration(rand.Int63n(int64(wait)))
|
|
wait = wait + jitter/2
|
|
time.Sleep(wait)
|
|
return makeXmlRequest(
|
|
retries, wait, client, method, params, pResult)
|
|
}
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
func genXMLMask(mask string) interface{} {
|
|
objectMask := map[string]interface{}{}
|
|
for _, item := range strings.Split(mask, ";") {
|
|
if !strings.Contains(item, ".") {
|
|
objectMask[item] = []string{}
|
|
continue
|
|
}
|
|
|
|
level := objectMask
|
|
names := strings.Split(item, ".")
|
|
totalNames := len(names)
|
|
for i, name := range names {
|
|
if i == totalNames-1 {
|
|
level[name] = []string{}
|
|
continue
|
|
}
|
|
|
|
level[name] = map[string]interface{}{}
|
|
level = level[name].(map[string]interface{})
|
|
}
|
|
}
|
|
|
|
return objectMask
|
|
}
|