-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtransport.go
More file actions
98 lines (81 loc) · 2.7 KB
/
transport.go
File metadata and controls
98 lines (81 loc) · 2.7 KB
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
package httpcache
import (
"errors"
"net/http"
)
var (
// ErrMissingCache will be returned if cache is not set when creating an
// instance of [Transport].
ErrMissingCache = errors.New("cache not set when creating transport")
// ErrMissingConfig will be returned if config is not set when creating an
// instance of [Transport].
ErrMissingConfig = errors.New("config not set when creating transport")
)
// Transport is the main interface of the package. It uses [Cache] to persist
// HTTP response, and and uses configuration values from [Config] to interpret
// requests and responses.
type Transport struct {
// transport handles HTTP requests. This is hardcoded to be
// [http.DefaultTransport].
transport http.RoundTripper
// cache handles persisting HTTP responses.
cache Cache
// config describes which HTTP responses to cache and how they are cached.
config *Config
}
// NewTransport creates a [Transport]. If the cache is nil, return
// [ErrMissingCache]. If the config is nil, return [ErrMissingConfig].
func NewTransport(config *Config, cache Cache) (*Transport, error) {
if cache == nil {
return nil, ErrMissingCache
}
if config == nil {
return nil, ErrMissingConfig
}
return &Transport{
transport: http.DefaultTransport,
cache: cache,
config: config,
}, nil
}
// RoundTrip wraps the [http.DefaultTransport] RoundTrip to execute HTTP
// requests and persists, if necessary, the responses. If [Cache] returns
// [ErrNoResponse], then execute a HTTP request and persist the response if
// passing the criteria in [Transport.shouldSaveResponse].
func (t *Transport) RoundTrip(request *http.Request) (*http.Response, error) {
response, err := t.cache.Read(request)
if err == nil {
return response, nil
}
if !errors.Is(err, ErrNoResponse) {
return nil, err
}
response, err = t.transport.RoundTrip(request)
if err != nil {
return nil, err
}
if t.shouldSaveResponse(response.StatusCode, response.Request.Method) {
err = t.cache.Save(response, t.config.ExpiryTime)
if err != nil {
response.Body.Close()
return nil, err
}
}
return response, nil
}
// shouldSaveResponse is responsible for interpreting configuration values from
// [Config] to determine if a HTTP response should be persisted. Any new values
// added to [Config] can be used here as criteria.
func (t *Transport) shouldSaveResponse(statusCode int, method string) bool {
isAllowedStatusCode := contains(t.config.AllowedStatusCodes, statusCode)
isAllowedMethod := contains(t.config.AllowedMethods, method)
return isAllowedStatusCode && isAllowedMethod
}
func contains[T comparable](slice []T, searchValue T) bool {
for _, value := range slice {
if value == searchValue {
return true
}
}
return false
}