1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
|
package httputil
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net"
"net/http"
"os"
)
// ErrorJSON is returned with "application/json" content type and non-2XX status code
type ErrorJSON struct {
Message string `json:"message"`
}
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
}
// HTTPStatusErrorBodyMaxLength specifies the maximum length of HTTPStatusError.Body
const HTTPStatusErrorBodyMaxLength = 64 * 1024
// HTTPStatusError is created from non-2XX HTTP response
type HTTPStatusError struct {
// StatusCode is non-2XX status code
StatusCode int
// Body is at most HTTPStatusErrorBodyMaxLength
Body string
}
// Error implements error.
// If e.Body is a marshalled string of api.ErrorJSON, Error returns ErrorJSON.Message .
// Otherwise Error returns a human-readable string that contains e.StatusCode and e.Body.
func (e *HTTPStatusError) Error() string {
if e.Body != "" && len(e.Body) < HTTPStatusErrorBodyMaxLength {
var ej 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)
}
// Successful returns an error if the status code is not 2xx.
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 NewHTTPClient(socketPath string) (*http.Client, error) {
if _, err := os.Stat(socketPath); err != nil {
return nil, err
}
return &http.Client{
Transport: &http.Transport{
DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
var d net.Dialer
return d.DialContext(ctx, "unix", socketPath)
},
},
}, nil
}
// WriteError writes an error.
// WriteError sould not be used if an error may contain sensitive information and the client is not reliable.
func WriteError(w http.ResponseWriter, r *http.Request, err error, ec int) {
w.WriteHeader(ec)
w.Header().Set("Content-Type", "application/json")
e := ErrorJSON{
Message: err.Error(),
}
_ = json.NewEncoder(w).Encode(e)
}
|