1
0
Fork 0
mirror of https://github.com/diamondburned/arikawa.git synced 2024-12-01 11:12:48 +00:00
arikawa/internal/httputil/client.go

180 lines
3.4 KiB
Go
Raw Normal View History

2020-01-15 04:56:50 +00:00
// Package httputil provides abstractions around the common needs of HTTP. It
// also allows swapping in and out the HTTP client.
2020-01-02 05:39:52 +00:00
package httputil
import (
"context"
"io"
"io/ioutil"
2020-01-19 03:12:08 +00:00
"mime/multipart"
2020-01-02 05:39:52 +00:00
"net/http"
"time"
2020-01-15 18:32:54 +00:00
"github.com/diamondburned/arikawa/internal/json"
2020-01-02 05:39:52 +00:00
)
// Retries is the default attempts to retry if the API returns an error before
// giving up.
var Retries uint = 5
2020-01-02 05:39:52 +00:00
type Client struct {
http.Client
json.Driver
2020-01-06 03:48:39 +00:00
SchemaEncoder
Retries uint
2020-01-02 05:39:52 +00:00
}
2020-01-15 04:43:34 +00:00
var DefaultClient = NewClient()
2020-01-02 05:39:52 +00:00
func NewClient() Client {
return Client{
Client: http.Client{
Timeout: 10 * time.Second,
},
2020-01-06 03:48:39 +00:00
Driver: json.Default{},
SchemaEncoder: &DefaultSchema{},
Retries: Retries,
2020-01-02 05:39:52 +00:00
}
}
2020-01-19 03:12:08 +00:00
func (c *Client) MeanwhileMultipart(
multipartWriter func(*multipart.Writer) error,
2020-01-02 05:39:52 +00:00
method, url string, opts ...RequestOption) (*http.Response, error) {
// We want to cancel the request if our bodyWriter fails
ctx, cancel := context.WithCancel(context.Background())
2020-01-19 02:27:30 +00:00
defer cancel()
2020-01-02 05:39:52 +00:00
r, w := io.Pipe()
2020-01-19 03:12:08 +00:00
body := multipart.NewWriter(w)
2020-01-02 05:39:52 +00:00
var bgErr error
go func() {
2020-01-19 03:12:08 +00:00
if err := multipartWriter(body); err != nil {
2020-01-02 05:39:52 +00:00
bgErr = err
cancel()
}
2020-01-19 02:27:30 +00:00
// Close the writer so the body gets flushed to the HTTP reader.
w.Close()
2020-01-02 05:39:52 +00:00
}()
resp, err := c.RequestCtx(ctx, method, url,
2020-01-19 03:12:08 +00:00
append([]RequestOption{
WithBody(r),
WithContentType(body.FormDataContentType()),
}, opts...)...)
2020-01-02 05:39:52 +00:00
if err != nil && bgErr != nil {
if resp.Body != nil {
resp.Body.Close()
}
return nil, bgErr
}
return resp, err
}
func (c *Client) FastRequest(
method, url string, opts ...RequestOption) error {
2020-01-02 05:39:52 +00:00
r, err := c.Request(method, url, opts...)
if err != nil {
return err
}
return r.Body.Close()
}
func (c *Client) RequestCtx(ctx context.Context,
method, url string, opts ...RequestOption) (*http.Response, error) {
req, err := http.NewRequestWithContext(ctx, method, url, nil)
if err != nil {
return nil, RequestError{err}
}
for _, opt := range opts {
if err := opt(req); err != nil {
return nil, err
}
}
var r *http.Response
for i := uint(0); i < c.Retries; i++ {
r, err = c.Client.Do(req)
if err != nil {
continue
}
if r.StatusCode < 200 || r.StatusCode > 299 {
continue
}
break
}
// If all retries failed:
2020-01-02 05:39:52 +00:00
if err != nil {
return nil, RequestError{err}
}
// Response received, but with a failure status code:
2020-01-02 05:39:52 +00:00
if r.StatusCode < 200 || r.StatusCode > 299 {
httpErr := &HTTPError{
Status: r.StatusCode,
}
b, err := ioutil.ReadAll(r.Body)
if err != nil {
return nil, httpErr
}
httpErr.Body = b
c.Unmarshal(b, &httpErr)
return nil, httpErr
}
return r, nil
}
func (c *Client) RequestCtxJSON(ctx context.Context,
to interface{}, method, url string, opts ...RequestOption) error {
r, err := c.RequestCtx(ctx, method, url,
append([]RequestOption{JSONRequest}, opts...)...)
if err != nil {
return err
}
defer r.Body.Close()
2020-01-19 02:27:30 +00:00
// No content, working as intended (tm)
if r.StatusCode == http.StatusNoContent {
return nil
}
2020-01-02 05:39:52 +00:00
if err := c.DecodeStream(r.Body, to); err != nil {
return JSONError{err}
}
return nil
}
func (c *Client) Request(
method, url string, opts ...RequestOption) (*http.Response, error) {
return c.RequestCtx(context.Background(), method, url, opts...)
}
func (c *Client) RequestJSON(
to interface{}, method, url string, opts ...RequestOption) error {
return c.RequestCtxJSON(context.Background(), to, method, url, opts...)
}