Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
lima-vm
GitHub Repository: lima-vm/lima
Path: blob/master/pkg/httpclientutil/httpclientutil.go
2611 views
1
// SPDX-FileCopyrightText: Copyright The Lima Authors
2
// SPDX-License-Identifier: Apache-2.0
3
4
package httpclientutil
5
6
// Forked from https://github.com/rootless-containers/rootlesskit/blob/v0.14.2/pkg/api/client/client.go
7
// Apache License 2.0
8
9
import (
10
"context"
11
"encoding/json"
12
"errors"
13
"fmt"
14
"io"
15
"net"
16
"net/http"
17
18
"github.com/lima-vm/lima/v2/pkg/httputil"
19
)
20
21
// Get calls HTTP GET and verifies that the status code is 2XX .
22
func Get(ctx context.Context, c *http.Client, url string) (*http.Response, error) {
23
req, err := http.NewRequestWithContext(ctx, "GET", url, http.NoBody)
24
if err != nil {
25
return nil, err
26
}
27
resp, err := c.Do(req)
28
if err != nil {
29
return nil, err
30
}
31
if err := Successful(resp); err != nil {
32
resp.Body.Close()
33
return nil, err
34
}
35
return resp, nil
36
}
37
38
func Head(ctx context.Context, c *http.Client, url string) (*http.Response, error) {
39
req, err := http.NewRequestWithContext(ctx, "HEAD", url, http.NoBody)
40
if err != nil {
41
return nil, err
42
}
43
resp, err := c.Do(req)
44
if err != nil {
45
return nil, err
46
}
47
if err := Successful(resp); err != nil {
48
resp.Body.Close()
49
return nil, err
50
}
51
return resp, nil
52
}
53
54
func Post(ctx context.Context, c *http.Client, url string, body io.Reader) (*http.Response, error) {
55
req, err := http.NewRequestWithContext(ctx, "POST", url, body)
56
if err != nil {
57
return nil, err
58
}
59
resp, err := c.Do(req)
60
if err != nil {
61
return nil, err
62
}
63
if err := Successful(resp); err != nil {
64
resp.Body.Close()
65
return nil, err
66
}
67
return resp, nil
68
}
69
70
func readAtMost(r io.Reader, maxBytes int) ([]byte, error) {
71
lr := &io.LimitedReader{
72
R: r,
73
N: int64(maxBytes),
74
}
75
b, err := io.ReadAll(lr)
76
if err != nil {
77
return b, err
78
}
79
if lr.N == 0 {
80
return b, fmt.Errorf("expected at most %d bytes, got more", maxBytes)
81
}
82
return b, nil
83
}
84
85
// HTTPStatusErrorBodyMaxLength specifies the maximum length of HTTPStatusError.Body.
86
const HTTPStatusErrorBodyMaxLength = 64 * 1024
87
88
// HTTPStatusError is created from non-2XX HTTP response.
89
type HTTPStatusError struct {
90
// StatusCode is non-2XX status code
91
StatusCode int
92
// Body is at most HTTPStatusErrorBodyMaxLength
93
Body string
94
}
95
96
// Error implements error.
97
// If e.Body is a marshalled string of httputil.ErrorJSON, Error returns ErrorJSON.Message .
98
// Otherwise Error returns a human-readable string that contains e.StatusCode and e.Body.
99
func (e *HTTPStatusError) Error() string {
100
if e.Body != "" && len(e.Body) < HTTPStatusErrorBodyMaxLength {
101
var ej httputil.ErrorJSON
102
if json.Unmarshal([]byte(e.Body), &ej) == nil {
103
return ej.Message
104
}
105
}
106
return fmt.Sprintf("unexpected HTTP status %s, body=%q", http.StatusText(e.StatusCode), e.Body)
107
}
108
109
func Successful(resp *http.Response) error {
110
if resp == nil {
111
return errors.New("nil response")
112
}
113
if resp.StatusCode/100 != 2 {
114
b, _ := readAtMost(resp.Body, HTTPStatusErrorBodyMaxLength)
115
return &HTTPStatusError{
116
StatusCode: resp.StatusCode,
117
Body: string(b),
118
}
119
}
120
return nil
121
}
122
123
// NewHTTPClientWithDialFn creates a client.
124
// conn is a raw net.Conn instance.
125
func NewHTTPClientWithDialFn(dialFn func(ctx context.Context) (net.Conn, error)) (*http.Client, error) {
126
hc := &http.Client{
127
Transport: &http.Transport{
128
DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
129
return dialFn(ctx)
130
},
131
},
132
}
133
return hc, nil
134
}
135
136