Last active
January 10, 2021 11:02
-
-
Save developer-guy/ae0c2199fba676f1ca0e848c2def4aa1 to your computer and use it in GitHub Desktop.
Custom HTTP client
This file contains hidden or 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
| package main | |
| import ( | |
| "bytes" | |
| "context" | |
| "encoding/json" | |
| "errors" | |
| "fmt" | |
| "io" | |
| "log" | |
| "net/http" | |
| "net/http/httputil" | |
| ) | |
| type Options struct { | |
| ApiURL string | |
| Verbose bool | |
| } | |
| type Client struct { | |
| httpClient *http.Client | |
| options *Options | |
| } | |
| func New(httpClient *http.Client, options Options) *Client { | |
| return &Client{ | |
| httpClient: httpClient, | |
| options: &options, | |
| } | |
| } | |
| type HTTPClient interface { | |
| Get(ctx context.Context, path string, v interface{}) error | |
| Post(ctx context.Context, path string, payload interface{}, v interface{}) error | |
| Put(ctx context.Context, path string, payload interface{}, v interface{}) error | |
| Delete(ctx context.Context, path string, payload interface{}, v interface{}) error | |
| } | |
| func (c *Client) Get(ctx context.Context, path string, v interface{}) error { | |
| req, err := c.newRequest(ctx, http.MethodGet, path, nil) | |
| if err != nil { | |
| return fmt.Errorf("failed to create GET request: %w", err) | |
| } | |
| if err := c.doRequest(req, v); err != nil { | |
| return err | |
| } | |
| return nil | |
| } | |
| func (c *Client) newRequest(ctx context.Context, method, path string, payload interface{}) (*http.Request, error) { | |
| var reqBody io.Reader | |
| if payload != nil { | |
| bodyBytes, err := json.Marshal(payload) | |
| if err != nil { | |
| return nil, fmt.Errorf("failed to marshal request body: %w", err) | |
| } | |
| reqBody = bytes.NewBuffer(bodyBytes) | |
| } | |
| req, err := http.NewRequest(method, fmt.Sprintf("%s%s", c.options.ApiURL, path), reqBody) | |
| if err != nil { | |
| return nil, fmt.Errorf("failed to create HTTP request: %w", err) | |
| } | |
| if c.options.Verbose { | |
| body, _ := httputil.DumpRequest(req, true) | |
| log.Println(fmt.Sprintf("%s", string(body))) | |
| } | |
| req = req.WithContext(ctx) | |
| return req, nil | |
| } | |
| func (c *Client) doRequest(r *http.Request, v interface{}) error { | |
| resp, err := c.do(r) | |
| if err != nil { | |
| return err | |
| } | |
| if resp == nil { | |
| return nil | |
| } | |
| defer resp.Body.Close() | |
| if v == nil { | |
| return nil | |
| } | |
| var buf bytes.Buffer | |
| dec := json.NewDecoder(io.TeeReader(resp.Body, &buf)) | |
| if err := dec.Decode(v); err != nil { | |
| return fmt.Errorf("could not parse response body: %w [%s:%s] %s", err, r.Method, r.URL.String(), buf.String()) | |
| } | |
| return nil | |
| } | |
| var ( | |
| ErrUserAccessDenied = errors.New("you do not have access to the requested resource") | |
| ErrNotFound = errors.New("the requested resource not found") | |
| ErrTooManyRequests = errors.New("you have exceeded throttle") | |
| ) | |
| func (c *Client) do(r *http.Request) (*http.Response, error) { | |
| resp, err := c.httpClient.Do(r) | |
| if err != nil { | |
| return nil, fmt.Errorf("failed to make request [%s:%s]: %w", r.Method, r.URL.String(), err) | |
| } | |
| if c.options.Verbose { | |
| body, _ := httputil.DumpResponse(resp, true) | |
| log.Println(fmt.Sprintf("%s", string(body))) | |
| } | |
| switch resp.StatusCode { | |
| case http.StatusOK, | |
| http.StatusCreated, | |
| http.StatusNoContent: | |
| return resp, nil | |
| } | |
| defer resp.Body.Close() | |
| switch resp.StatusCode { | |
| case http.StatusNotFound: | |
| return nil, ErrNotFound | |
| case http.StatusUnauthorized, | |
| http.StatusForbidden: | |
| return nil, ErrUserAccessDenied | |
| case http.StatusTooManyRequests: | |
| return nil, ErrTooManyRequests | |
| } | |
| return nil, fmt.Errorf("failed to do request, %d status code received", resp.StatusCode) | |
| } | |
| type Response struct { | |
| Headers map[string]interface{} `json:"headers"` | |
| } | |
| func main() { | |
| httpClient := &http.Client{} | |
| client := New(httpClient, Options{ | |
| ApiURL: "http://httpbin.org/anything", | |
| Verbose: true, | |
| }) | |
| var response Response | |
| err := client.Get(context.Background(), "/anything", &response) | |
| if err != nil { | |
| log.Fatalf("could not get response from path: %s, detail: %s", "/anything", err.Error()) | |
| } | |
| log.Println(response.Headers) | |
| } |
Author
developer-guy
commented
Jan 10, 2021
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment