-
Notifications
You must be signed in to change notification settings - Fork 0
/
rcutil.go
191 lines (170 loc) · 4.26 KB
/
rcutil.go
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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
package rcutil
import (
"bufio"
"bytes"
"encoding/gob"
"errors"
"io"
"net/http"
"net/url"
"path/filepath"
"strings"
)
const (
CacheResultHeader = "X-Cache"
CacheHit = "HIT"
CacheMiss = "MISS"
)
var ErrNoRequest = errors.New("no request")
var ErrInvalidRequest = errors.New("invalid request")
// Seed returns seed for cache key.
// The return value seed is NOT path-safe.
func Seed(req *http.Request, vary []string) (string, error) {
if req == nil {
return "", ErrNoRequest
}
if req.URL == nil {
return "", ErrInvalidRequest
}
if req.Host == "" {
return "", ErrInvalidRequest
}
const sep = "|"
// Use req.Host ( does not use req.URL.Host )
// See https://httpwg.org/specs/rfc9110.html#rfc.section.7.1 and https://httpwg.org/specs/rfc9110.html#rfc.section.7.2
seed := req.Method + sep + req.Host + sep + req.URL.Path + sep + req.URL.RawQuery
for _, h := range vary {
if vv := req.Header.Get(h); vv != "" {
seed += sep + h + ":" + vv
}
}
return strings.ToLower(seed), nil
}
// EncodeReq encodes http.Request.
func EncodeReq(req *http.Request, w io.Writer) error {
return req.Write(w)
}
// EncodeRes encodes http.Response.
func EncodeRes(res *http.Response, w io.Writer) error {
return res.Write(w)
}
// DecodeReq decodes to http.Request
func DecodeReq(r io.Reader) (*http.Request, error) {
return http.ReadRequest(bufio.NewReader(r))
}
// DecodeRes decodes to http.Response
func DecodeRes(r io.Reader) (*http.Response, error) {
return http.ReadResponse(bufio.NewReader(r), nil)
}
type cachedReqRes struct {
Method string `json:"method"`
Host string `json:"host"`
URL string `json:"url"`
ReqHeader http.Header `json:"req_header"`
ReqBody []byte `json:"req_body"`
StatusCode int `json:"status_code"`
ResHeader http.Header `json:"res_header"`
ResBody []byte `json:"res_body"`
}
// EncodeReqRes encodes http.Request and http.Response.
// Depracated: Use EncodeReq and EncodeRes instead.
func EncodeReqRes(req *http.Request, res *http.Response, w io.Writer) error {
c := &cachedReqRes{
Method: req.Method,
Host: req.Host,
URL: req.URL.String(),
ReqHeader: req.Header,
StatusCode: res.StatusCode,
ResHeader: res.Header,
}
{
// FIXME: Use stream
b, err := io.ReadAll(req.Body)
if err != nil {
return err
}
defer req.Body.Close()
c.ReqBody = b
}
{
// FIXME: Use stream
b, err := io.ReadAll(res.Body)
if err != nil {
return err
}
defer res.Body.Close()
c.ResBody = b
}
if err := gob.NewEncoder(w).Encode(c); err != nil {
return err
}
return nil
}
// DecodeReqRes decodes to http.Request and http.Response.
// Depracated: Use DecodeReq and DecodeRes instead.
func DecodeReqRes(r io.Reader) (*http.Request, *http.Response, error) {
c := &cachedReqRes{}
if err := gob.NewDecoder(r).Decode(c); err != nil {
return nil, nil, err
}
u, err := url.Parse(c.URL)
if err != nil {
return nil, nil, err
}
req := &http.Request{
Method: c.Method,
Host: c.Host,
URL: u,
Header: c.ReqHeader,
Body: io.NopCloser(bytes.NewReader(c.ReqBody)),
}
res := &http.Response{
Status: http.StatusText(c.StatusCode),
StatusCode: c.StatusCode,
Header: c.ResHeader,
Body: io.NopCloser(bytes.NewReader(c.ResBody)),
}
return req, res, nil
}
// KeyToPath converts key to path
// It is the responsibility of the user to pass path-safe keys
func KeyToPath(key string, n int) string {
if n <= 0 {
return key
}
var result strings.Builder
l := len(key)
for i, char := range key {
if i > 0 && i%n == 0 && l-i > 0 {
result.WriteRune(filepath.Separator)
}
result.WriteRune(char)
}
return result.String()
}
// PathToKey converts path to key
func PathToKey(path string) string {
return strings.ReplaceAll(path, string(filepath.Separator), "")
}
// WriteCounter counts bytes written.
type WriteCounter struct {
io.Writer
Bytes uint64
}
// Write writes bytes.
func (wc *WriteCounter) Write(p []byte) (int, error) {
n, err := wc.Writer.Write(p)
if err != nil {
return n, err
}
wc.Bytes += uint64(n)
return n, err
}
// SetCacheResultHeader sets cache header.
func SetCacheResultHeader(res *http.Response, hit bool) {
if hit {
res.Header.Set(CacheResultHeader, CacheHit)
} else {
res.Header.Set(CacheResultHeader, CacheMiss)
}
}