ruby-http-session 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,196 @@
1
+ class HTTP::Session
2
+ class Cache
3
+ # Parses a cache-control header and exposes the directives as a Hash.
4
+ # Directives that do not have values are set to +true+.
5
+ #
6
+ # Mostly borrowed from [rack-cache/lib/rack/cache/cache_control.rb](https://github.com/rack/rack-cache/blob/main/lib/rack/cache/cache_control.rb)
7
+ class CacheControl < Hash
8
+ def initialize(value = nil)
9
+ parse(value)
10
+ end
11
+
12
+ # Indicates that the response MAY be cached by any cache, even if it
13
+ # would normally be non-cacheable or cacheable only within a non-
14
+ # shared cache.
15
+ #
16
+ # A response may be considered public without this directive if the
17
+ # private directive is not set and the request does not include an
18
+ # Authorization header.
19
+ def public?
20
+ self["public"]
21
+ end
22
+
23
+ # Indicates that all or part of the response message is intended for
24
+ # a single user and MUST NOT be cached by a shared cache. This
25
+ # allows an origin server to state that the specified parts of the
26
+ # response are intended for only one user and are not a valid
27
+ # response for requests by other users. A private (non-shared) cache
28
+ # MAY cache the response.
29
+ #
30
+ # Note: This usage of the word private only controls where the
31
+ # response may be cached, and cannot ensure the privacy of the
32
+ # message content.
33
+ def private?
34
+ self["private"]
35
+ end
36
+
37
+ # When set in a response, a cache MUST NOT use the response to satisfy a
38
+ # subsequent request without successful revalidation with the origin
39
+ # server. This allows an origin server to prevent caching even by caches
40
+ # that have been configured to return stale responses to client requests.
41
+ #
42
+ # Note that this does not necessary imply that the response may not be
43
+ # stored by the cache, only that the cache cannot serve it without first
44
+ # making a conditional GET request with the origin server.
45
+ #
46
+ # When set in a request, the server MUST NOT use a cached copy for its
47
+ # response. This has quite different semantics compared to the no-cache
48
+ # directive on responses. When the client specifies no-cache, it causes
49
+ # an end-to-end reload, forcing each cache to update their cached copies.
50
+ def no_cache?
51
+ self["no-cache"]
52
+ end
53
+
54
+ # Indicates that the response MUST NOT be stored under any circumstances.
55
+ #
56
+ # The purpose of the no-store directive is to prevent the
57
+ # inadvertent release or retention of sensitive information (for
58
+ # example, on backup tapes). The no-store directive applies to the
59
+ # entire message, and MAY be sent either in a response or in a
60
+ # request. If sent in a request, a cache MUST NOT store any part of
61
+ # either this request or any response to it. If sent in a response,
62
+ # a cache MUST NOT store any part of either this response or the
63
+ # request that elicited it. This directive applies to both non-
64
+ # shared and shared caches. "MUST NOT store" in this context means
65
+ # that the cache MUST NOT intentionally store the information in
66
+ # non-volatile storage, and MUST make a best-effort attempt to
67
+ # remove the information from volatile storage as promptly as
68
+ # possible after forwarding it.
69
+ #
70
+ # The purpose of this directive is to meet the stated requirements
71
+ # of certain users and service authors who are concerned about
72
+ # accidental releases of information via unanticipated accesses to
73
+ # cache data structures. While the use of this directive might
74
+ # improve privacy in some cases, we caution that it is NOT in any
75
+ # way a reliable or sufficient mechanism for ensuring privacy. In
76
+ # particular, malicious or compromised caches might not recognize or
77
+ # obey this directive, and communications networks might be
78
+ # vulnerable to eavesdropping.
79
+ def no_store?
80
+ self["no-store"]
81
+ end
82
+
83
+ # The expiration time of an entity MAY be specified by the origin
84
+ # server using the expires header (see section 14.21). Alternatively,
85
+ # it MAY be specified using the max-age directive in a response. When
86
+ # the max-age cache-control directive is present in a cached response,
87
+ # the response is stale if its current age is greater than the age
88
+ # value given (in seconds) at the time of a new request for that
89
+ # resource. The max-age directive on a response implies that the
90
+ # response is cacheable (i.e., "public") unless some other, more
91
+ # restrictive cache directive is also present.
92
+ #
93
+ # If a response includes both an expires header and a max-age
94
+ # directive, the max-age directive overrides the expires header, even
95
+ # if the expires header is more restrictive. This rule allows an origin
96
+ # server to provide, for a given response, a longer expiration time to
97
+ # an HTTP/1.1 (or later) cache than to an HTTP/1.0 cache. This might be
98
+ # useful if certain HTTP/1.0 caches improperly calculate ages or
99
+ # expiration times, perhaps due to desynchronized clocks.
100
+ #
101
+ # Many HTTP/1.0 cache implementations will treat an expires value that
102
+ # is less than or equal to the response Date value as being equivalent
103
+ # to the cache-control response directive "no-cache". If an HTTP/1.1
104
+ # cache receives such a response, and the response does not include a
105
+ # cache-control header field, it SHOULD consider the response to be
106
+ # non-cacheable in order to retain compatibility with HTTP/1.0 servers.
107
+ #
108
+ # When the max-age directive is included in the request, it indicates
109
+ # that the client is willing to accept a response whose age is no
110
+ # greater than the specified time in seconds.
111
+ def max_age
112
+ self["max-age"].to_i if key?("max-age")
113
+ end
114
+
115
+ # If a response includes an s-maxage directive, then for a shared
116
+ # cache (but not for a private cache), the maximum age specified by
117
+ # this directive overrides the maximum age specified by either the
118
+ # max-age directive or the expires header. The s-maxage directive
119
+ # also implies the semantics of the proxy-revalidate directive. i.e.,
120
+ # that the shared cache must not use the entry after it becomes stale
121
+ # to respond to a subsequent request without first revalidating it with
122
+ # the origin server. The s-maxage directive is always ignored by a
123
+ # private cache.
124
+ def shared_max_age
125
+ self["s-maxage"].to_i if key?("s-maxage")
126
+ end
127
+ alias_method :s_maxage, :shared_max_age
128
+
129
+ # Because a cache MAY be configured to ignore a server's specified
130
+ # expiration time, and because a client request MAY include a max-
131
+ # stale directive (which has a similar effect), the protocol also
132
+ # includes a mechanism for the origin server to require revalidation
133
+ # of a cache entry on any subsequent use. When the must-revalidate
134
+ # directive is present in a response received by a cache, that cache
135
+ # MUST NOT use the entry after it becomes stale to respond to a
136
+ # subsequent request without first revalidating it with the origin
137
+ # server. (I.e., the cache MUST do an end-to-end revalidation every
138
+ # time, if, based solely on the origin server's expires or max-age
139
+ # value, the cached response is stale.)
140
+ #
141
+ # The must-revalidate directive is necessary to support reliable
142
+ # operation for certain protocol features. In all circumstances an
143
+ # HTTP/1.1 cache MUST obey the must-revalidate directive; in
144
+ # particular, if the cache cannot reach the origin server for any
145
+ # reason, it MUST generate a 504 (Gateway Timeout) response.
146
+ #
147
+ # Servers SHOULD send the must-revalidate directive if and only if
148
+ # failure to revalidate a request on the entity could result in
149
+ # incorrect operation, such as a silently unexecuted financial
150
+ # transaction. Recipients MUST NOT take any automated action that
151
+ # violates this directive, and MUST NOT automatically provide an
152
+ # unvalidated copy of the entity if revalidation fails.
153
+ def must_revalidate?
154
+ self["must-revalidate"]
155
+ end
156
+
157
+ # The proxy-revalidate directive has the same meaning as the must-
158
+ # revalidate directive, except that it does not apply to non-shared
159
+ # user agent caches. It can be used on a response to an
160
+ # authenticated request to permit the user's cache to store and
161
+ # later return the response without needing to revalidate it (since
162
+ # it has already been authenticated once by that user), while still
163
+ # requiring proxies that service many users to revalidate each time
164
+ # (in order to make sure that each user has been authenticated).
165
+ # Note that such authenticated responses also need the public cache
166
+ # control directive in order to allow them to be cached at all.
167
+ def proxy_revalidate?
168
+ self["proxy-revalidate"]
169
+ end
170
+
171
+ def to_s
172
+ bools, vals = [], []
173
+ each do |key, value|
174
+ if value == true
175
+ bools << key
176
+ elsif value
177
+ vals << "#{key}=#{value}"
178
+ end
179
+ end
180
+ (bools.sort + vals.sort).join(", ")
181
+ end
182
+
183
+ private
184
+
185
+ def parse(value)
186
+ return if value.nil? || value.empty?
187
+ value.delete(" ").split(",").each do |part|
188
+ next if part.empty?
189
+ name, value = part.split("=", 2)
190
+ self[name.downcase] = (value || true) unless name.empty?
191
+ end
192
+ self
193
+ end
194
+ end
195
+ end
196
+ end
@@ -0,0 +1,71 @@
1
+ class HTTP::Session
2
+ class Cache
3
+ class Entry
4
+ class << self
5
+ # Deserializes from a JSON primitive type.
6
+ def deserialize(h)
7
+ req = h[:req]
8
+ req = HTTP::Session::Request.new(HTTP::Request.new(req))
9
+
10
+ res = h[:res]
11
+ res[:request] = req
12
+ res[:body] = HTTP::Session::Response::StringBody.new(res[:body])
13
+ res = HTTP::Session::Response.new(HTTP::Response.new(res))
14
+
15
+ new(req, res)
16
+ end
17
+ end
18
+
19
+ # @!attribute [r] request
20
+ # @return [Request]
21
+ attr_reader :request
22
+
23
+ # @!attribute [r] response
24
+ # @return [Response]
25
+ attr_reader :response
26
+
27
+ # Returns a new instance of Entry.
28
+ def initialize(req, res)
29
+ @request = req
30
+ @response = res
31
+ end
32
+
33
+ # @param [Request] req │
34
+ # @return [Response]
35
+ def to_response(req)
36
+ h = serialize_response
37
+ h[:request] = req
38
+ h[:body] = HTTP::Session::Response::StringBody.new(h[:body])
39
+ HTTP::Session::Response.new(HTTP::Response.new(h))
40
+ end
41
+
42
+ # Serializes to a JSON primitive type.
43
+ def serialize
44
+ {
45
+ req: serialize_request,
46
+ res: serialize_response
47
+ }
48
+ end
49
+
50
+ private
51
+
52
+ def serialize_request
53
+ {
54
+ verb: @request.verb,
55
+ uri: @request.uri.to_s,
56
+ headers: @request.headers.to_h
57
+ }
58
+ end
59
+
60
+ def serialize_response
61
+ {
62
+ status: @response.status.code,
63
+ version: @response.version,
64
+ headers: @response.headers.to_h,
65
+ proxy_headers: @response.proxy_headers.to_h,
66
+ body: @response.body.to_s
67
+ }
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,20 @@
1
+ class HTTP::Session
2
+ class Cache
3
+ class Status
4
+ # rubocop:disable Layout/ExtraSpacing
5
+ HEADER_NAME = "X-Httprb-Cache-Status"
6
+ HIT = "HIT" # found in cache
7
+ REVALIDATED = "REVALIDATED" # found in cache but stale, revalidated success
8
+ EXPIRED = "EXPIRED" # found in cache but stale, revalidated failure, served from the origin server
9
+ MISS = "MISS" # not found in cache, served from the origin server
10
+ UNCACHEABLE = "UNCACHEABLE" # the request can not use cached response
11
+ # rubocop:enable Layout/ExtraSpacing
12
+
13
+ class << self
14
+ def HIT?(v)
15
+ v == HIT || v == REVALIDATED
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,95 @@
1
+ class HTTP::Session
2
+ class Cache
3
+ include MonitorMixin
4
+
5
+ # @param [Options::CacheOption] options
6
+ def initialize(options)
7
+ super()
8
+
9
+ @options = options
10
+ end
11
+
12
+ # Read an entry from cache.
13
+ #
14
+ # @param [Request] req
15
+ # @return [Entry]
16
+ def read(req)
17
+ synchronize do
18
+ key = cache_key_for(req)
19
+ entries = read_entries(key)
20
+ entries.find { |e| entry_matched?(e, req) }
21
+ end
22
+ end
23
+
24
+ # Write an entry to cache.
25
+ #
26
+ # @param [Request] req
27
+ # @param [Response] res
28
+ # @return [void]
29
+ def write(req, res)
30
+ synchronize do
31
+ key = cache_key_for(req)
32
+ entries = read_entries(key)
33
+ entries = entries.reject { |e| entry_matched?(e, req) }
34
+ entry = HTTP::Session::Cache::Entry.new(req, res)
35
+ entries << entry
36
+ write_entries(key, entries)
37
+ end
38
+ end
39
+
40
+ # True when it is a shared cache.
41
+ def shared?
42
+ @options.shared_cache?
43
+ end
44
+
45
+ # True when it is a private cache.
46
+ def private?
47
+ @options.private_cache?
48
+ end
49
+
50
+ # @!visibility private
51
+ def store
52
+ @options.store
53
+ end
54
+
55
+ private
56
+
57
+ def entry_matched?(entry, req)
58
+ entry_matched_by_verb?(entry, req) &&
59
+ entry_matched_by_headers?(entry, req)
60
+ end
61
+
62
+ def entry_matched_by_verb?(entry, req)
63
+ entry.request.verb == req.verb
64
+ end
65
+
66
+ def entry_matched_by_headers?(entry, req)
67
+ vary = entry.response.headers[HTTP::Headers::VARY]
68
+ return true if vary.nil? || vary == ""
69
+
70
+ vary != "*" && vary.split(",").map(&:strip).all? do |name|
71
+ entry.request.headers[name] == req.headers[name]
72
+ end
73
+ end
74
+
75
+ def read_entries(key)
76
+ entrie = store.read(key) || []
77
+ entrie.map do |e|
78
+ Entry.deserialize(e)
79
+ end
80
+ end
81
+
82
+ def write_entries(key, entries)
83
+ entries = entries.map do |e|
84
+ e = e.serialize
85
+ e[:res][:headers].delete(HTTP::Headers::AGE)
86
+ e
87
+ end
88
+ store.write(key, entries)
89
+ end
90
+
91
+ def cache_key_for(req)
92
+ Digest::SHA256.hexdigest(req.uri)
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,143 @@
1
+ class HTTP::Session
2
+ class Client
3
+ # Make an HTTP request without any HTTP::Features.
4
+ #
5
+ # Mostly borrowed from [http/lib/http/client.rb](https://github.com/httprb/http/blob/main/lib/http/client.rb)
6
+ module Perform
7
+ HTTP_OR_HTTPS_RE = %r{^https?://}i
8
+
9
+ attr_reader :default_options
10
+
11
+ def httprb_initialize(default_options)
12
+ @default_options = HTTP::Options.new(default_options)
13
+ @connection = nil
14
+ @state = :clean
15
+ end
16
+
17
+ def httprb_perform(req, options)
18
+ verify_connection!(req.uri)
19
+
20
+ @state = :dirty
21
+ begin
22
+ @connection ||= HTTP::Connection.new(req, options)
23
+ unless @connection.failed_proxy_connect?
24
+ @connection.send_request(req)
25
+ @connection.read_headers!
26
+ end
27
+ rescue HTTP::Error => e
28
+ options.features.each_value do |feature|
29
+ feature.on_error(req, e)
30
+ end
31
+ raise
32
+ end
33
+
34
+ res = build_response(req, options)
35
+ @connection.finish_response if req.verb == :head
36
+ @state = :clean
37
+ res
38
+ rescue
39
+ close
40
+ raise
41
+ end
42
+
43
+ private
44
+
45
+ def verify_connection!(uri)
46
+ if default_options.persistent? && uri.origin != default_options.persistent
47
+ raise HTTP::StateError, "Persistence is enabled for #{default_options.persistent}, but we got #{uri.origin}"
48
+ end
49
+ return close if @connection && (!@connection.keep_alive? || @connection.expired?)
50
+ close if @state == :dirty
51
+ end
52
+
53
+ def close
54
+ @connection&.close
55
+ @connection = nil
56
+ @state = :clean
57
+ end
58
+
59
+ def build_response(req, options)
60
+ res = HTTP::Response.new(
61
+ status: @connection.status_code,
62
+ version: @connection.http_version,
63
+ headers: @connection.headers,
64
+ proxy_headers: @connection.proxy_response_headers,
65
+ connection: @connection,
66
+ encoding: options.encoding,
67
+ request: req
68
+ )
69
+ HTTP::Session::Response.new(res)
70
+ end
71
+
72
+ def wrap_response(res, opts)
73
+ opts.features.to_a.reverse.to_h.inject(res) do |response, (_name, feature)|
74
+ response = feature.wrap_response(response)
75
+ HTTP::Session::Response.new(response)
76
+ end
77
+ end
78
+
79
+ def build_request(verb, uri, opts = {})
80
+ opts = @default_options.merge(opts)
81
+ uri = make_request_uri(uri, opts)
82
+ headers = make_request_headers(opts)
83
+ body = make_request_body(opts, headers)
84
+ req = HTTP::Request.new(
85
+ verb: verb,
86
+ uri: uri,
87
+ uri_normalizer: opts.feature(:normalize_uri)&.normalizer,
88
+ proxy: opts.proxy,
89
+ headers: headers,
90
+ body: body
91
+ )
92
+ HTTP::Session::Request.new(req)
93
+ end
94
+
95
+ def wrap_request(req, opts)
96
+ opts.features.inject(req) do |request, (_name, feature)|
97
+ request = feature.wrap_request(request)
98
+ HTTP::Session::Request.new(request)
99
+ end
100
+ end
101
+
102
+ def make_request_uri(uri, opts)
103
+ uri = uri.to_s
104
+ uri = "#{default_options.persistent}#{uri}" if default_options.persistent? && uri !~ HTTP_OR_HTTPS_RE
105
+ uri = HTTP::URI.parse uri
106
+ uri.query_values = uri.query_values(Array).to_a.concat(opts.params.to_a) if opts.params && !opts.params.empty?
107
+ uri.path = "/" if uri.path.empty?
108
+ uri
109
+ end
110
+
111
+ def make_request_headers(opts)
112
+ headers = opts.headers
113
+ headers[HTTP::Headers::CONNECTION] = default_options.persistent? ? HTTP::Connection::KEEP_ALIVE : HTTP::Connection::CLOSE
114
+ cookies = opts.cookies.values
115
+ unless cookies.empty?
116
+ cookies = opts.headers.get(HTTP::Headers::COOKIE).concat(cookies).join("; ")
117
+ headers[HTTP::Headers::COOKIE] = cookies
118
+ end
119
+ headers
120
+ end
121
+
122
+ def make_request_body(opts, headers)
123
+ if opts.body
124
+ opts.body
125
+ elsif opts.form
126
+ form = make_form_data(opts.form)
127
+ headers[HTTP::Headers::CONTENT_TYPE] ||= form.content_type
128
+ form
129
+ elsif opts.json
130
+ body = HTTP::MimeType[:json].encode opts.json
131
+ headers[HTTP::Headers::CONTENT_TYPE] ||= "application/json; charset=#{body.encoding.name.downcase}"
132
+ body
133
+ end
134
+ end
135
+
136
+ def make_form_data(form)
137
+ return form if form.is_a? HTTP::FormData::Multipart
138
+ return form if form.is_a? HTTP::FormData::Urlencoded
139
+ HTTP::FormData.create(form)
140
+ end
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,157 @@
1
+ class HTTP::Session
2
+ class Client
3
+ include HTTP::Session::Client::Perform
4
+
5
+ # @param [Hash] default_options
6
+ # @param [Session] session
7
+ def initialize(default_options, session)
8
+ httprb_initialize(default_options)
9
+ @session = session
10
+ end
11
+
12
+ # @param verb
13
+ # @param uri
14
+ # @param [Hash] opts
15
+ # @return [Response]
16
+ def request(verb, uri, opts)
17
+ data = @session.make_http_request_data
18
+ hist = []
19
+
20
+ opts = @default_options.merge(opts)
21
+ opts = _hs_handle_http_request_options_cookies(opts, data[:cookies])
22
+ opts = _hs_handle_http_request_options_follow(opts, hist)
23
+
24
+ req = build_request(verb, uri, opts)
25
+ res = perform(req, opts)
26
+ return res unless opts.follow
27
+
28
+ HTTP::Redirector.new(opts.follow).perform(req, res) do |request|
29
+ request = HTTP::Session::Request.new(request)
30
+ perform(request, opts)
31
+ end.tap do |res|
32
+ res.history = hist
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ # Make an HTTP request.
39
+ #
40
+ # @param [Request] req
41
+ # @param [HTTP::Options] opts
42
+ # @return [Response]
43
+ def perform(req, opts)
44
+ req = wrap_request(req, opts)
45
+ wrap_response(_hs_perform(req, opts), opts)
46
+ end
47
+
48
+ # Add session cookie to the request's :cookies.
49
+ def _hs_handle_http_request_options_cookies(opts, cookies)
50
+ return opts if cookies.nil?
51
+ opts.with_cookies(cookies)
52
+ end
53
+
54
+ # Wrap the :on_redirect method in the request's :follow.
55
+ def _hs_handle_http_request_options_follow(opts, hist)
56
+ return opts unless opts.follow
57
+
58
+ follow = (opts.follow == true) ? {} : opts.follow
59
+ opts.with_follow(follow.merge(
60
+ on_redirect: _hs_handle_http_request_options_follow_hijack(follow[:on_redirect], hist)
61
+ ))
62
+ end
63
+
64
+ # Wrap the :on_redirect method.
65
+ def _hs_handle_http_request_options_follow_hijack(fn, hist)
66
+ lambda do |res, req|
67
+ hist << res
68
+ fn.call(res, req) if fn.respond_to?(:call)
69
+ end
70
+ end
71
+
72
+ # Make an HTTP request using cache.
73
+ def _hs_perform(req, opts)
74
+ if @session.default_options.cache.enabled?
75
+ req.cacheable? ? _hs_cache_lookup(req, opts) : _hs_cache_pass(req, opts)
76
+ else
77
+ _hs_forward(req, opts)
78
+ end
79
+ end
80
+
81
+ # Try to serve the response from cache.
82
+ #
83
+ # * When a matching cache entry is found and is fresh, use it as the response
84
+ # without forwarding any request to the backend.
85
+ # * When a matching cache entry is found but is stale, attempt to validate the
86
+ # entry with the backend using conditional GET.
87
+ # * When no matching cache entry is found, trigger miss processing.
88
+ def _hs_cache_lookup(req, opts)
89
+ entry = @session.cache.read(req)
90
+ if entry.nil?
91
+ _hs_cache_fetch(req, opts)
92
+ elsif entry.response.fresh?(shared: @session.cache.shared?) &&
93
+ !entry.response.no_cache? &&
94
+ !req.no_cache?
95
+ _hs_cache_reuse(req, opts, entry)
96
+ else
97
+ _hs_cache_validate(req, opts, entry)
98
+ end
99
+ end
100
+
101
+ # The cache entry is missing. Forward the request to the backend and determine
102
+ # whether the response should be stored.
103
+ def _hs_cache_fetch(req, opts)
104
+ res = _hs_forward(req, opts)
105
+ _hs_cache_entry_store(req, res)
106
+
107
+ res.headers[HTTP::Session::Cache::Status::HEADER_NAME] = HTTP::Session::Cache::Status::MISS
108
+ res
109
+ end
110
+
111
+ # The cache entry is fresh, reuse it.
112
+ def _hs_cache_reuse(req, opts, entry)
113
+ res = entry.to_response(req)
114
+ res.headers[HTTP::Headers::AGE] = [(res.now - res.date).to_i, 0].max.to_s
115
+ res.headers[HTTP::Session::Cache::Status::HEADER_NAME] = HTTP::Session::Cache::Status::HIT
116
+ res
117
+ end
118
+
119
+ # The cache entry is stale, revalidate it. The original request is used
120
+ # as a template for a conditional GET request with the backend.
121
+ def _hs_cache_validate(req, opts, entry)
122
+ req.headers[HTTP::Headers::IF_MODIFIED_SINCE] = entry.response.last_modified if entry.response.last_modified
123
+ req.headers[HTTP::Headers::IF_NONE_MATCH] = entry.response.etag if entry.response.etag
124
+
125
+ res = _hs_forward(req, opts)
126
+ if res.status.not_modified?
127
+ res = entry.to_response(req)
128
+ res.headers[HTTP::Session::Cache::Status::HEADER_NAME] = HTTP::Session::Cache::Status::REVALIDATED
129
+ return res
130
+ end
131
+
132
+ _hs_cache_entry_store(req, res)
133
+ res.headers[HTTP::Session::Cache::Status::HEADER_NAME] = HTTP::Session::Cache::Status::EXPIRED
134
+ res
135
+ end
136
+
137
+ # The request is not cacheable. So the request is sent to the backend, and the
138
+ # backend's response is sent to the client, but is not entered into the cache.
139
+ def _hs_cache_pass(req, opts)
140
+ res = _hs_forward(req, opts)
141
+ res.headers[HTTP::Session::Cache::Status::HEADER_NAME] = HTTP::Session::Cache::Status::UNCACHEABLE
142
+ res
143
+ end
144
+
145
+ # Store the response to cache.
146
+ def _hs_cache_entry_store(req, res)
147
+ if res.cacheable?(shared: @session.cache.shared?, req: req)
148
+ @session.cache.write(req, res)
149
+ end
150
+ end
151
+
152
+ # Delegate the request to the backend and create the response.
153
+ def _hs_forward(req, opts)
154
+ httprb_perform(req, opts)
155
+ end
156
+ end
157
+ end