package http1
import (
"bufio"
"errors"
"fmt"
"strconv"
"strings"
)
// Response is the decoded form of an HTTP/1.x response including body.
type Response struct {
Version string
Status int
Reason string
Headers [][2]string
Body []byte
Trailers [][2]string
Incomplete bool
}
var (
errBadStatus = errors.New("http1: status code out of range")
)
// ReadResponse parses one response from r. Unlike ReadRequest, the body
// framing can depend on the request method (e.g. HEAD has none); callers
// that know this can bypass body reading by passing isHEAD=true via the
// Decoder pair instead of calling ReadResponse directly.
func ReadResponse(r *bufio.Reader) (*Response, error) {
line, err := readCRLF(r)
if err != nil {
return nil, err
}
resp, err := parseStatusLine(line)
if err != nil {
return nil, err
}
h, err := ReadHeaders(r)
if err != nil {
return nil, err
}
resp.Headers = h
body, trailers, err := ReadBody(r, h, true)
if err != nil {
return nil, err
}
resp.Body = body
resp.Trailers = trailers
return resp, nil
}
// parseStatusLine reads "HTTP/1.1 SP STATUS SP REASON".
func parseStatusLine(line string) (*Response, error) {
line = strings.TrimRight(line, "\r\n")
parts := strings.SplitN(line, " ", 3)
if len(parts) < 2 {
return nil, fmt.Errorf("http1: bad status line: %q", line)
}
version := parts[0]
if !strings.HasPrefix(version, "HTTP/1.") {
return nil, fmt.Errorf("http1: bad version in status: %q", version)
}
code, err := strconv.Atoi(strings.TrimSpace(parts[1]))
if err != nil {
return nil, fmt.Errorf("http1: bad status code %q: %w", parts[1], err)
}
if code < 100 || code > 599 {
return nil, fmt.Errorf("%w: %d", errBadStatus, code)
}
reason := ""
if len(parts) == 3 {
reason = parts[2]
}
return &Response{
Version: version,
Status: code,
Reason: reason,
}, nil
}
// StatusClass maps a numeric code to its 1xx/2xx/3xx/4xx/5xx bucket.
func StatusClass(code int) string {
switch {
case code >= 100 && code < 200:
return "informational"
case code < 300:
return "success"
case code < 400:
return "redirect"
case code < 500:
return "client-error"
case code < 600:
return "server-error"
default:
return "unknown"
}
}