package spartan import ( "bufio" "bytes" "errors" "io" "strconv" "sync" "tildegit.org/tjp/sliderule/internal/types" ) // The spartan response types. const ( StatusSuccess types.Status = 2 StatusRedirect types.Status = 3 StatusClientError types.Status = 4 StatusServerError types.Status = 5 ) // Success builds a successful spartan response. func Success(mediatype string, body io.Reader) *types.Response { return &types.Response{ Status: StatusSuccess, Meta: mediatype, Body: body, } } // Redirect builds a spartan redirect response. func Redirect(url string) *types.Response { return &types.Response{ Status: StatusRedirect, Meta: url, } } // ClientError builds a "client error" spartan response. func ClientError(err error) *types.Response { return &types.Response{ Status: StatusClientError, Meta: err.Error(), } } // ServerError builds a "server error" spartan response. func ServerError(err error) *types.Response { return &types.Response{ Status: StatusServerError, Meta: err.Error(), } } // InvalidResponseHeaderLine indicates a malformed spartan response line. var InvalidResponseHeaderLine = errors.New("Invalid response header line.") // InvalidResponseLineEnding indicates that a spartan response header didn't end with "\r\n". var InvalidResponseLineEnding = errors.New("Invalid response line ending.") func ParseResponse(rdr io.Reader) (*types.Response, error) { bufrdr := bufio.NewReader(rdr) hdrLine, err := bufrdr.ReadString('\n') if err != nil { return nil, InvalidResponseLineEnding } if len(hdrLine) < 2 { return nil, InvalidResponseHeaderLine } status, err := strconv.Atoi(string(hdrLine[0])) if err != nil || hdrLine[1] != ' ' || hdrLine[len(hdrLine)-2:] != "\r\n" { return nil, InvalidResponseHeaderLine } return &types.Response{ Status: types.Status(status), Meta: hdrLine[2 : len(hdrLine)-2], Body: bufrdr, }, nil } // NewResponseReader builds a reader for a response. func NewResponseReader(response *types.Response) types.ResponseReader { return &responseReader{ Response: response, once: &sync.Once{}, } } type responseReader struct { *types.Response reader io.Reader once *sync.Once } func (rdr *responseReader) Read(b []byte) (int, error) { rdr.ensureReader() return rdr.reader.Read(b) } func (rdr *responseReader) WriteTo(dst io.Writer) (int64, error) { rdr.ensureReader() return rdr.reader.(io.WriterTo).WriteTo(dst) } func (rdr *responseReader) ensureReader() { rdr.once.Do(func() { hdr := bytes.NewBuffer(rdr.headerLine()) if rdr.Body != nil { rdr.reader = io.MultiReader(hdr, rdr.Body) } else { rdr.reader = hdr } }) } func (rdr *responseReader) headerLine() []byte { meta := rdr.Meta.(string) buf := make([]byte, len(meta)+4) buf[0] = byte(rdr.Status) + '0' buf[1] = ' ' copy(buf[2:], meta) buf[len(buf)-2] = '\r' buf[len(buf)-1] = '\n' return buf }