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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
|
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(),
}
}
// ErrInvalidResponseHeaderLine indicates a malformed spartan response line.
var ErrInvalidResponseHeaderLine = errors.New("invalid response header line")
// ErrInvalidResponseLineEnding indicates that a spartan response header didn't end with "\r\n".
var ErrInvalidResponseLineEnding = 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, ErrInvalidResponseLineEnding
}
if len(hdrLine) < 2 {
return nil, ErrInvalidResponseHeaderLine
}
status, err := strconv.Atoi(string(hdrLine[0]))
if err != nil || hdrLine[1] != ' ' || hdrLine[len(hdrLine)-2:] != "\r\n" {
return nil, ErrInvalidResponseHeaderLine
}
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
}
|