-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactored to avoid reaching API batch request limit; go fmtd
- Loading branch information
Showing
4 changed files
with
373 additions
and
313 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,141 +1,119 @@ | ||
package api | ||
|
||
import ( | ||
"bytes" | ||
"encoding/json" | ||
"fmt" | ||
"io/ioutil" | ||
"log" | ||
"net/http" | ||
"net/url" | ||
"encoding/json" | ||
"errors" | ||
"fmt" | ||
"net/http" | ||
"net/url" | ||
) | ||
|
||
const ( | ||
API_URL = "https://api.linode.com/" | ||
ApiUrl = "https://api.linode.com/" | ||
MaxBatchRequests = 25 | ||
) | ||
|
||
type queryParams map[string]string | ||
|
||
type apiAction struct { | ||
method string | ||
params queryParams | ||
method string | ||
params queryParams | ||
} | ||
|
||
func (self *apiAction) Set(key, value string) { | ||
self.params[key] = value | ||
self.params[key] = value | ||
} | ||
|
||
func (self apiAction) values() queryParams { | ||
self.params["api_action"] = self.method | ||
return self.params | ||
self.params["api_action"] = self.method | ||
return self.params | ||
} | ||
|
||
type apiRequest struct { | ||
apiKey string | ||
baseUrl *url.URL | ||
actions []*apiAction | ||
apiKey string | ||
baseUrl *url.URL | ||
actions []*apiAction | ||
} | ||
|
||
func NewApiRequest(apiKey string) (*apiRequest, error) { | ||
apiUrl, err := url.Parse(API_URL) | ||
if err != nil { | ||
return nil, err | ||
} | ||
var actions []*apiAction | ||
return &apiRequest{apiKey: apiKey, baseUrl: apiUrl, actions: actions}, nil | ||
apiUrl, err := url.Parse(ApiUrl) | ||
if err != nil { | ||
return nil, err | ||
} | ||
var actions []*apiAction | ||
return &apiRequest{apiKey: apiKey, baseUrl: apiUrl, actions: actions}, nil | ||
} | ||
|
||
func (self *apiRequest) AddAction(method string) *apiAction { | ||
action := &apiAction{method: method, params: make(queryParams)} | ||
self.actions = append(self.actions, action) | ||
return action | ||
action := &apiAction{method: method, params: make(queryParams)} | ||
self.actions = append(self.actions, action) | ||
return action | ||
} | ||
|
||
func (self apiRequest) URL() string { | ||
params := make(url.Values) | ||
params.Set("api_key", self.apiKey) | ||
|
||
if len(self.actions) == 1 { | ||
for key, value := range self.actions[0].values() { | ||
params.Set(key, value) | ||
} | ||
} else if len(self.actions) > 1 { | ||
params.Set("api_action", "batch") | ||
var requestArray []queryParams | ||
for _, action := range self.actions { | ||
requestArray = append(requestArray, action.values()) | ||
} | ||
b, err := json.Marshal(requestArray) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
params.Set("api_requestArray", string(b)) | ||
} | ||
self.baseUrl.RawQuery = params.Encode() | ||
return self.baseUrl.String() | ||
func (self apiRequest) URL() []string { | ||
actionBatches := make([][]*apiAction, (len(self.actions)/MaxBatchRequests)+1) | ||
for j, action := range self.actions { | ||
i := j / MaxBatchRequests | ||
actionBatches[i] = append(actionBatches[i], action) | ||
} | ||
|
||
var urls []string | ||
for _, actions := range actionBatches { | ||
params := make(url.Values) | ||
params.Set("api_key", self.apiKey) | ||
params.Set("api_action", "batch") | ||
var requestArray []queryParams | ||
for _, action := range actions { | ||
requestArray = append(requestArray, action.values()) | ||
} | ||
b, _ := json.Marshal(requestArray) | ||
params.Set("api_requestArray", string(b)) | ||
u := self.baseUrl | ||
u.RawQuery = params.Encode() | ||
urls = append(urls, u.String()) | ||
} | ||
return urls | ||
} | ||
|
||
func (self apiRequest) GetJson(data interface{}) error { | ||
resp, err := http.Get(self.URL()) | ||
if err != nil { | ||
return err | ||
} | ||
defer resp.Body.Close() | ||
|
||
body, err := ioutil.ReadAll(resp.Body) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// the linode API does not use HTTP status codes to indicate errors, | ||
// rather it embeds in the JSON document the errors. When there is an error | ||
// the foramt of the `DATA` element changes as well, which would cause the json decode to fail. | ||
// | ||
// Here we first parse the json to see if it contains errors, then re-parse with the provided | ||
// json structure | ||
if linodeErr := checkForLinodeError(bytes.NewReader(body)); linodeErr != nil { | ||
return linodeErr | ||
} | ||
|
||
decoder := json.NewDecoder(bytes.NewReader(body)) | ||
err = decoder.Decode(data) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
type apiResponse struct { | ||
Action string `json:"ACTION"` | ||
Errors []struct { | ||
Code int `json:"ERRORCODE"` | ||
Message string `json:"ERRORMESSAGE"` | ||
} `json:"ERRORARRAY,omitempty"` | ||
Data json.RawMessage `json:"DATA,omitempty"` | ||
} | ||
|
||
type errorJson struct { | ||
Errors []struct { | ||
Code int `json:"ERRORCODE"` | ||
Message string `json:"ERRORMESSAGE"` | ||
} `json:"ERRORARRAY,omitempty"` | ||
} | ||
|
||
func checkForLinodeError(body *bytes.Reader) error { | ||
data := new(errorJson) | ||
decoder := json.NewDecoder(body) | ||
err := decoder.Decode(&data) | ||
if err != nil { | ||
// this is not actually an error, since there is not always an error present in the JSON | ||
return nil | ||
} | ||
if len(data.Errors) > 0 { | ||
var buf bytes.Buffer | ||
buf.WriteString("Api Error!\n") | ||
for _, e := range data.Errors { | ||
buf.WriteString(fmt.Sprintf("[Code: %d] %s\n", e.Code, e.Message)) | ||
} | ||
return fmt.Errorf(buf.String()) | ||
} | ||
return nil | ||
} | ||
|
||
func (self *apiRequest) GoString() string { | ||
s, err := url.QueryUnescape(self.URL()) | ||
if err != nil { | ||
return "" | ||
} | ||
return s | ||
func (self apiRequest) GetJson() ([]json.RawMessage, []error) { | ||
var datas []json.RawMessage | ||
var errs []error | ||
|
||
for _, url := range self.URL() { | ||
resp, err := http.Get(url) | ||
if err != nil { | ||
return nil, []error{err} | ||
} | ||
defer resp.Body.Close() | ||
|
||
decoder := json.NewDecoder(resp.Body) | ||
|
||
var apiResponses []apiResponse | ||
err = decoder.Decode(&apiResponses) | ||
if err != nil { | ||
return nil, []error{err} | ||
} | ||
|
||
for _, apiResp := range apiResponses { | ||
// Check for 'ERROR' attribute for any values, which would indicate an error | ||
if len(apiResp.Errors) > 0 { | ||
for _, e := range apiResp.Errors { | ||
errs = append(errs, errors.New(fmt.Sprintf("[Code: %d] %s\n", e.Code, e.Message))) | ||
} | ||
continue | ||
} | ||
datas = append(datas, apiResp.Data) | ||
} | ||
} | ||
|
||
return datas, errs | ||
} |
Oops, something went wrong.