package httpclientutil
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net"
"net/http"
"github.com/lima-vm/lima/v2/pkg/httputil"
)
func Get(ctx context.Context, c *http.Client, url string) (*http.Response, error) {
req, err := http.NewRequestWithContext(ctx, "GET", url, http.NoBody)
if err != nil {
return nil, err
}
resp, err := c.Do(req)
if err != nil {
return nil, err
}
if err := Successful(resp); err != nil {
resp.Body.Close()
return nil, err
}
return resp, nil
}
func Head(ctx context.Context, c *http.Client, url string) (*http.Response, error) {
req, err := http.NewRequestWithContext(ctx, "HEAD", url, http.NoBody)
if err != nil {
return nil, err
}
resp, err := c.Do(req)
if err != nil {
return nil, err
}
if err := Successful(resp); err != nil {
resp.Body.Close()
return nil, err
}
return resp, nil
}
func Post(ctx context.Context, c *http.Client, url string, body io.Reader) (*http.Response, error) {
req, err := http.NewRequestWithContext(ctx, "POST", url, body)
if err != nil {
return nil, err
}
resp, err := c.Do(req)
if err != nil {
return nil, err
}
if err := Successful(resp); err != nil {
resp.Body.Close()
return nil, err
}
return resp, nil
}
func readAtMost(r io.Reader, maxBytes int) ([]byte, error) {
lr := &io.LimitedReader{
R: r,
N: int64(maxBytes),
}
b, err := io.ReadAll(lr)
if err != nil {
return b, err
}
if lr.N == 0 {
return b, fmt.Errorf("expected at most %d bytes, got more", maxBytes)
}
return b, nil
}
const HTTPStatusErrorBodyMaxLength = 64 * 1024
type HTTPStatusError struct {
StatusCode int
Body string
}
func (e *HTTPStatusError) Error() string {
if e.Body != "" && len(e.Body) < HTTPStatusErrorBodyMaxLength {
var ej httputil.ErrorJSON
if json.Unmarshal([]byte(e.Body), &ej) == nil {
return ej.Message
}
}
return fmt.Sprintf("unexpected HTTP status %s, body=%q", http.StatusText(e.StatusCode), e.Body)
}
func Successful(resp *http.Response) error {
if resp == nil {
return errors.New("nil response")
}
if resp.StatusCode/100 != 2 {
b, _ := readAtMost(resp.Body, HTTPStatusErrorBodyMaxLength)
return &HTTPStatusError{
StatusCode: resp.StatusCode,
Body: string(b),
}
}
return nil
}
func NewHTTPClientWithDialFn(dialFn func(ctx context.Context) (net.Conn, error)) (*http.Client, error) {
hc := &http.Client{
Transport: &http.Transport{
DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
return dialFn(ctx)
},
},
}
return hc, nil
}