13 "github.com/Sirupsen/logrus"
16 type HTTPClient struct {
17 httpClient http.Client
28 type HTTPClientConfig struct {
30 HeaderAPIKeyName string
32 HeaderClientKeyName string
43 // Inspired by syncthing/cmd/cli
45 const insecure = false
47 // HTTPNewClient creates a new HTTP client to deal with Syncthing
48 func HTTPNewClient(baseURL string, cfg HTTPClientConfig) (*HTTPClient, error) {
50 // Create w new Http client
51 httpClient := http.Client{
52 Transport: &http.Transport{
53 TLSClientConfig: &tls.Config{
54 InsecureSkipVerify: insecure,
59 httpClient: httpClient,
63 /* TODO - add user + pwd support
64 username: c.GlobalString("username"),
65 password: c.GlobalString("password"),
69 if client.apikey == "" {
70 if err := client.getCidAndCsrf(); err != nil {
77 // SetLogger Define the logger to use
78 func (c *HTTPClient) SetLogger(log *logrus.Logger) {
82 func (c *HTTPClient) log(level int, format string, args ...interface{}) {
86 c.logger.Errorf(format, args...)
89 c.logger.Warningf(format, args...)
92 c.logger.Infof(format, args...)
95 c.logger.Debugf(format, args...)
101 // Send request to retrieve Client id and/or CSRF token
102 func (c *HTTPClient) getCidAndCsrf() error {
103 request, err := http.NewRequest("GET", c.endpoint, nil)
107 if _, err := c.handleRequest(request); err != nil {
111 return errors.New("Failed to get device ID")
113 if !c.conf.CsrfDisable && c.csrf == "" {
114 return errors.New("Failed to get CSRF token")
119 // GetClientID returns the id
120 func (c *HTTPClient) GetClientID() string {
124 // formatURL Build full url by concatenating all parts
125 func (c *HTTPClient) formatURL(endURL string) string {
127 if !strings.HasSuffix(url, "/") {
130 url += strings.TrimLeft(c.conf.URLPrefix, "/")
131 if !strings.HasSuffix(url, "/") {
134 return url + strings.TrimLeft(endURL, "/")
137 // HTTPGet Send a Get request to client and return an error object
138 func (c *HTTPClient) HTTPGet(url string, data *[]byte) error {
139 _, err := c.HTTPGetWithRes(url, data)
143 // HTTPGetWithRes Send a Get request to client and return both response and error
144 func (c *HTTPClient) HTTPGetWithRes(url string, data *[]byte) (*http.Response, error) {
145 request, err := http.NewRequest("GET", c.formatURL(url), nil)
149 res, err := c.handleRequest(request)
153 if res.StatusCode != 200 {
154 return res, errors.New(res.Status)
157 *data = c.responseToBArray(res)
162 // HTTPPost Send a POST request to client and return an error object
163 func (c *HTTPClient) HTTPPost(url string, body string) error {
164 _, err := c.HTTPPostWithRes(url, body)
168 // HTTPPostWithRes Send a POST request to client and return both response and error
169 func (c *HTTPClient) HTTPPostWithRes(url string, body string) (*http.Response, error) {
170 request, err := http.NewRequest("POST", c.formatURL(url), bytes.NewBufferString(body))
174 res, err := c.handleRequest(request)
178 if res.StatusCode != 200 {
179 return res, errors.New(res.Status)
184 func (c *HTTPClient) responseToBArray(response *http.Response) []byte {
185 defer response.Body.Close()
186 bytes, err := ioutil.ReadAll(response.Body)
188 // TODO improved error reporting
189 fmt.Println("ERROR: " + err.Error())
194 func (c *HTTPClient) handleRequest(request *http.Request) (*http.Response, error) {
195 if c.conf.HeaderAPIKeyName != "" && c.apikey != "" {
196 request.Header.Set(c.conf.HeaderAPIKeyName, c.apikey)
198 if c.conf.HeaderClientKeyName != "" && c.id != "" {
199 request.Header.Set(c.conf.HeaderClientKeyName, c.id)
201 if c.username != "" || c.password != "" {
202 request.SetBasicAuth(c.username, c.password)
205 request.Header.Set("X-CSRF-Token-"+c.id[:5], c.csrf)
208 c.log(logDebug, "HTTP %s %v", request.Method, request.URL)
210 response, err := c.httpClient.Do(request)
215 // Detect client ID change
216 cid := response.Header.Get(c.conf.HeaderClientKeyName)
217 if cid != "" && c.id != cid {
221 // Detect CSR token change
222 for _, item := range response.Cookies() {
223 if item.Name == "CSRF-Token-"+c.id[:5] {
231 if response.StatusCode == 404 {
232 return nil, errors.New("Invalid endpoint or API call")
233 } else if response.StatusCode == 401 {
234 return nil, errors.New("Invalid username or password")
235 } else if response.StatusCode == 403 {
237 // Request a new Csrf for next requests
239 return nil, errors.New("Invalid CSRF token")
241 return nil, errors.New("Invalid API key")
242 } else if response.StatusCode != 200 {
243 data := make(map[string]interface{})
244 // Try to decode error field of APIError struct
245 json.Unmarshal(c.responseToBArray(response), &data)
246 if err, found := data["error"]; found {
247 return nil, fmt.Errorf(err.(string))
249 body := strings.TrimSpace(string(c.responseToBArray(response)))
251 return nil, fmt.Errorf(body)
254 return nil, errors.New("Unknown HTTP status returned: " + response.Status)