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