Client for the pixeldrain API. Used by pixeldrain itself for tranferring data between the web UI and API server. And for rendering JSON responses
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

204 lines
5.1 KiB

  1. package pixelapi
  2. import (
  3. "context"
  4. "encoding/json"
  5. "fmt"
  6. "io"
  7. "io/ioutil"
  8. "net"
  9. "net/http"
  10. "net/url"
  11. "strings"
  12. "time"
  13. )
  14. // PixelAPI is the Pixeldrain API client
  15. type PixelAPI struct {
  16. client *http.Client
  17. apiEndpoint string
  18. key string
  19. realIP string
  20. realAgent string
  21. }
  22. // New creates a new Pixeldrain API client to query the Pixeldrain API with
  23. func New(apiEndpoint string) (api PixelAPI) {
  24. api.client = &http.Client{Timeout: time.Minute * 5}
  25. api.apiEndpoint = apiEndpoint
  26. // Pixeldrain uses unix domain sockets on its servers to minimize latency
  27. // between the web interface daemon and API daemon. Golang does not
  28. // understand that it needs to dial a unix socket on this case so we create
  29. // a custom HTTP transport which uses the unix socket instead of TCP
  30. if strings.HasPrefix(apiEndpoint, "http://unix:") {
  31. // Get the socket path from the API endpoint
  32. var sockPath = strings.TrimPrefix(apiEndpoint, "http://unix:")
  33. // Fake the dialer to use a unix socket instead of TCP
  34. api.client.Transport = &http.Transport{
  35. DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
  36. return net.Dial("unix", sockPath)
  37. },
  38. }
  39. // Fake a domain name to stop Go's HTTP client from complaining about
  40. // the domain name. This string will be completely ignored during
  41. // requests
  42. api.apiEndpoint = "http://api.sock"
  43. } else {
  44. api.client.Transport = http.DefaultTransport
  45. }
  46. return api
  47. }
  48. // Login logs a user into the pixeldrain API. The original PixelAPI does not get
  49. // logged in, only the returned PixelAPI
  50. func (p PixelAPI) Login(apiKey string) PixelAPI {
  51. p.key = apiKey
  52. return p
  53. }
  54. // RealIP sets the real IP address to use when making API requests
  55. func (p PixelAPI) RealIP(ip string) PixelAPI {
  56. p.realIP = ip
  57. return p
  58. }
  59. // RealAgent sets the real user agent to use when making API requests
  60. func (p PixelAPI) RealAgent(agent string) PixelAPI {
  61. p.realAgent = agent
  62. return p
  63. }
  64. // Standard response types
  65. // Error is an error returned by the pixeldrain API. If the request failed
  66. // before it could reach the API the error will be on a different type
  67. type Error struct {
  68. Status int `json:"-"` // One of the http.Status types
  69. Success bool `json:"success"`
  70. StatusCode string `json:"value"`
  71. Message string `json:"message"`
  72. // In case of the multiple_errors code this array will be populated with
  73. // more errors
  74. Errors []Error `json:"errors,omitempty"`
  75. // Metadata regarding the error
  76. Extra map[string]interface{} `json:"extra,omitempty"`
  77. }
  78. func (e Error) Error() string { return e.StatusCode }
  79. // ErrIsServerError returns true if the error is a server-side error
  80. func ErrIsServerError(err error) bool {
  81. if apierr, ok := err.(Error); ok && apierr.Status >= 500 {
  82. return true
  83. }
  84. return false
  85. }
  86. // ErrIsClientError returns true if the error is a client-side error
  87. func ErrIsClientError(err error) bool {
  88. if apierr, ok := err.(Error); ok && apierr.Status >= 400 && apierr.Status < 500 {
  89. return true
  90. }
  91. return false
  92. }
  93. func (p *PixelAPI) do(r *http.Request) (*http.Response, error) {
  94. if p.key != "" {
  95. r.SetBasicAuth("", p.key)
  96. }
  97. if p.realIP != "" {
  98. r.Header.Set("X-Real-IP", p.realIP)
  99. }
  100. if p.realAgent != "" {
  101. r.Header.Set("User-Agent", p.realAgent)
  102. }
  103. return p.client.Do(r)
  104. }
  105. func (p *PixelAPI) getString(path string) (string, error) {
  106. req, err := http.NewRequest("GET", p.apiEndpoint+"/"+path, nil)
  107. if err != nil {
  108. return "", err
  109. }
  110. resp, err := p.do(req)
  111. if err != nil {
  112. return "", err
  113. }
  114. defer resp.Body.Close()
  115. bodyBytes, err := ioutil.ReadAll(resp.Body)
  116. return string(bodyBytes), err
  117. }
  118. func (p *PixelAPI) getRaw(path string) (io.ReadCloser, error) {
  119. req, err := http.NewRequest("GET", p.apiEndpoint+"/"+path, nil)
  120. if err != nil {
  121. return nil, err
  122. }
  123. resp, err := p.do(req)
  124. if err != nil {
  125. return nil, err
  126. }
  127. return resp.Body, err
  128. }
  129. func (p *PixelAPI) jsonRequest(method, path string, target interface{}) error {
  130. req, err := http.NewRequest(method, p.apiEndpoint+"/"+path, nil)
  131. if err != nil {
  132. return err
  133. }
  134. resp, err := p.do(req)
  135. if err != nil {
  136. return err
  137. }
  138. defer resp.Body.Close()
  139. return parseJSONResponse(resp, target)
  140. }
  141. func (p *PixelAPI) form(method, url string, vals url.Values, target interface{}) error {
  142. req, err := http.NewRequest(method, p.apiEndpoint+"/"+url, strings.NewReader(vals.Encode()))
  143. if err != nil {
  144. return fmt.Errorf("prepare request failed: %w", err)
  145. }
  146. req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
  147. resp, err := p.do(req)
  148. if err != nil {
  149. return fmt.Errorf("do request failed: %w", err)
  150. }
  151. defer resp.Body.Close()
  152. return parseJSONResponse(resp, target)
  153. }
  154. func parseJSONResponse(resp *http.Response, target interface{}) (err error) {
  155. // Test for client side and server side errors
  156. if resp.StatusCode >= 400 {
  157. errResp := Error{Status: resp.StatusCode}
  158. if err = json.NewDecoder(resp.Body).Decode(&errResp); err != nil {
  159. return fmt.Errorf("failed to decode json error: %w", err)
  160. }
  161. return errResp
  162. }
  163. if target == nil {
  164. return nil
  165. }
  166. if err = json.NewDecoder(resp.Body).Decode(target); err != nil {
  167. return fmt.Errorf("failed to decode json response: %w", err)
  168. }
  169. return nil
  170. }