github.com/1and1/oneandone-cloudserver-sdk-go@v1.4.1/restclient.go (about) 1 package oneandone 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "io" 9 "io/ioutil" 10 "net/http" 11 p_url "net/url" 12 "time" 13 ) 14 15 type restClient struct { 16 token string 17 transport http.RoundTripper 18 } 19 20 func newRestClient(token string) *restClient { 21 restClient := new(restClient) 22 restClient.token = token 23 return restClient 24 } 25 26 func (c *restClient) SetTransport(transport http.RoundTripper) { 27 c.transport = transport 28 } 29 30 func (c *restClient) Get(url string, result interface{}, expectedStatus int) error { 31 return c.doRequest(url, "GET", nil, result, expectedStatus) 32 } 33 34 func (c *restClient) Delete(url string, requestBody interface{}, result interface{}, expectedStatus int) error { 35 return c.doRequest(url, "DELETE", requestBody, result, expectedStatus) 36 } 37 38 func (c *restClient) Post(url string, requestBody interface{}, result interface{}, expectedStatus int) error { 39 return c.doRequest(url, "POST", requestBody, result, expectedStatus) 40 } 41 42 func (c *restClient) Put(url string, requestBody interface{}, result interface{}, expectedStatus int) error { 43 return c.doRequest(url, "PUT", requestBody, result, expectedStatus) 44 } 45 46 func (c *restClient) doRequest(url string, method string, requestBody interface{}, result interface{}, expectedStatus int) error { 47 maxRetries := 5 48 retriesCount := 0 49 50 for { 51 52 var bodyData io.Reader 53 if requestBody != nil { 54 data, err := json.Marshal(requestBody) 55 if err != nil { 56 return err 57 } 58 bodyData = bytes.NewBuffer(data) 59 } 60 request, err := http.NewRequest(method, url, bodyData) 61 if err != nil { 62 return err 63 } 64 request.Header.Add("X-Token", c.token) 65 request.Header.Add("Content-Type", "application/json") 66 67 client := http.Client{} 68 if c.transport != nil { 69 client.Transport = c.transport 70 } 71 72 response, err := client.Do(request) 73 74 if err != nil { 75 _, ok := err.(*p_url.Error) 76 if ok { 77 //EOF error, sending a retry 78 if retriesCount < maxRetries { 79 retriesCount++ 80 time.Sleep(3 * time.Second) 81 continue 82 } 83 } 84 return err 85 } 86 87 if response != nil { 88 defer response.Body.Close() 89 body, err := ioutil.ReadAll(response.Body) 90 if err != nil { 91 return err 92 } 93 if response.StatusCode == http.StatusTooManyRequests { 94 retryAfter := response.Header.Get("X-Rate-Limit-Reset") 95 if retryAfter == "" { 96 //let the SDK sleep for 60 seconds while the API send the correct header 97 retryAfter = "60" 98 } 99 100 sleep, err := time.ParseDuration(retryAfter + "s") 101 if err != nil { 102 return err 103 } 104 105 time.Sleep(sleep) 106 107 } else if response.StatusCode != expectedStatus { 108 erResp := &errorResponse{} 109 err = json.Unmarshal(body, erResp) 110 if err != nil { 111 return err 112 } 113 return ApiError{response.StatusCode, fmt.Sprintf("Type: %s; Message: %s", erResp.Type, erResp.Message)} 114 } else { 115 return c.unmarshal(body, result) 116 } 117 } 118 } 119 } 120 121 func (c *restClient) unmarshal(data []byte, result interface{}) error { 122 err := json.Unmarshal(data, result) 123 if err != nil { 124 // handle the case when the result is an empty array instead of an object 125 switch err.(type) { 126 case *json.UnmarshalTypeError: 127 var ra []interface{} 128 e := json.Unmarshal(data, &ra) 129 if e != nil { 130 return e 131 } else if len(ra) > 0 { 132 return err 133 } 134 return nil 135 default: 136 return err 137 } 138 } 139 140 return nil 141 } 142 143 func createUrl(api *API, sections ...interface{}) string { 144 url := api.Endpoint 145 for _, section := range sections { 146 url += "/" + fmt.Sprint(section) 147 } 148 return url 149 } 150 151 func makeParameterMap(args ...interface{}) (map[string]interface{}, error) { 152 qps := make(map[string]interface{}, len(args)) 153 var is_true bool 154 var page, per_page int 155 var sort, query, fields string 156 157 for i, p := range args { 158 switch i { 159 case 0: 160 page, is_true = p.(int) 161 if !is_true { 162 return nil, errors.New("1st parameter must be a page number (integer).") 163 } else if page > 0 { 164 qps["page"] = page 165 } 166 case 1: 167 per_page, is_true = p.(int) 168 if !is_true { 169 return nil, errors.New("2nd parameter must be a per_page number (integer).") 170 } else if per_page > 0 { 171 qps["per_page"] = per_page 172 } 173 case 2: 174 sort, is_true = p.(string) 175 if !is_true { 176 return nil, errors.New("3rd parameter must be a sorting property string (e.g. 'name' or '-name').") 177 } else if sort != "" { 178 qps["sort"] = sort 179 } 180 case 3: 181 query, is_true = p.(string) 182 if !is_true { 183 return nil, errors.New("4th parameter must be a query string to look for the response.") 184 } else if query != "" { 185 qps["q"] = query 186 } 187 case 4: 188 fields, is_true = p.(string) 189 if !is_true { 190 return nil, errors.New("5th parameter must be fields properties string (e.g. 'id,name').") 191 } else if fields != "" { 192 qps["fields"] = fields 193 } 194 default: 195 return nil, errors.New("Wrong number of parameters.") 196 } 197 } 198 return qps, nil 199 } 200 201 func processQueryParams(url string, args ...interface{}) (string, error) { 202 if len(args) > 0 { 203 params, err := makeParameterMap(args...) 204 if err != nil { 205 return "", err 206 } 207 url = appendQueryParams(url, params) 208 } 209 return url, nil 210 } 211 212 func processQueryParamsExt(url string, period string, sd *time.Time, ed *time.Time, args ...interface{}) (string, error) { 213 var qm map[string]interface{} 214 var err error 215 if len(args) > 0 { 216 qm, err = makeParameterMap(args...) 217 if err != nil { 218 return "", err 219 } 220 } else { 221 qm = make(map[string]interface{}, 3) 222 } 223 qm["period"] = period 224 if sd != nil && ed != nil { 225 if sd.After(*ed) { 226 return "", errors.New("Start date cannot be after end date.") 227 } 228 qm["start_date"] = sd.Format(time.RFC3339) 229 qm["end_date"] = ed.Format(time.RFC3339) 230 } 231 url = appendQueryParams(url, qm) 232 return url, nil 233 } 234 235 func appendQueryParams(url string, params map[string]interface{}) string { 236 queryUrl, _ := p_url.Parse(url) 237 parameters := p_url.Values{} 238 for key, value := range params { 239 parameters.Add(key, fmt.Sprintf("%v", value)) 240 } 241 queryUrl.RawQuery = parameters.Encode() 242 return queryUrl.String() 243 }