Class: HTTP::Features::Caching
- Inherits:
-
HTTP::Feature
- Object
- HTTP::Feature
- HTTP::Features::Caching
- Defined in:
- lib/http/features/caching.rb,
lib/http/features/caching/entry.rb,
lib/http/features/caching/in_memory_store.rb,
sig/http.rbs
Overview
HTTP caching feature that stores and reuses responses according to
RFC 7234. Only GET and HEAD responses are cached. Supports
Cache-Control, Expires, ETag, and Last-Modified for freshness
checks and conditional revalidation.
Defined Under Namespace
Classes: Entry, InMemoryStore
Constant Summary collapse
- CACHEABLE_METHODS =
Set.new(%i[get head]).freeze
Instance Attribute Summary collapse
-
#store ⇒ #lookup, #store
readonly
The cache store instance.
Instance Method Summary collapse
-
#add_conditional_headers(request, entry) ⇒ HTTP::Request
private
Add conditional headers from a cached entry to the request.
-
#around_request(request) {|arg0| ... } ⇒ HTTP::Response
Wraps the HTTP exchange with caching logic.
-
#build_cached_response(entry, request) ⇒ HTTP::Response
private
Build a response from a cached entry.
-
#build_entry(response, body_string) ⇒ Entry
private
Build a cache entry from a response.
-
#cacheable_request?(request) ⇒ Boolean
private
Check whether this request method is cacheable.
-
#cacheable_response?(response) ⇒ Boolean
private
Check whether this response is cacheable.
-
#freshness_info?(response, directives) ⇒ Boolean
private
Whether the response carries enough information to determine freshness.
-
#initialize(store: InMemoryStore.new) ⇒ Caching
constructor
Initializes the Caching feature.
-
#now ⇒ Time
private
Current time (extracted for testability).
-
#parse_cache_control(headers) ⇒ Array<String>
private
Parse Cache-Control header into a list of directives.
-
#revalidate_entry(entry, response, request) ⇒ HTTP::Response
private
Revalidate a cached entry with a 304 response.
-
#store_and_freeze_response(response) ⇒ HTTP::Response
private
Store response in cache and return a new response with eagerly-read body.
-
#wrap_response(response) ⇒ HTTP::Response
Stores cacheable responses in the cache.
Methods inherited from HTTP::Feature
#on_error, #on_request, #wrap_request
Constructor Details
#initialize(store: InMemoryStore.new) ⇒ Caching
Initializes the Caching feature
44 45 46 |
# File 'lib/http/features/caching.rb', line 44 def initialize(store: InMemoryStore.new) @store = store end |
Instance Attribute Details
#store ⇒ #lookup, #store (readonly)
The cache store instance
34 35 36 |
# File 'lib/http/features/caching.rb', line 34 def store @store end |
Instance Method Details
#add_conditional_headers(request, entry) ⇒ HTTP::Request
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Add conditional headers from a cached entry to the request
177 178 179 180 181 182 183 184 185 186 187 188 189 190 |
# File 'lib/http/features/caching.rb', line 177 def add_conditional_headers(request, entry) headers = request.headers.dup headers[Headers::IF_NONE_MATCH] = entry.headers[Headers::ETAG] # steep:ignore headers[Headers::IF_MODIFIED_SINCE] = entry.headers[Headers::LAST_MODIFIED] # steep:ignore Request.new( verb: request.verb, uri: request.uri, headers: headers, proxy: request.proxy, body: request.body, version: request.version ) end |
#around_request(request) {|arg0| ... } ⇒ HTTP::Response
Wraps the HTTP exchange with caching logic
Checks the cache before making a request. Returns a cached response if fresh; otherwise adds conditional headers and revalidates. Stores cacheable responses for future use.
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
# File 'lib/http/features/caching.rb', line 62 def around_request(request) return yield(request) unless cacheable_request?(request) entry = store.lookup(request) return yield(request) unless entry return build_cached_response(entry, request) if entry.fresh? response = yield(add_conditional_headers(request, entry)) return revalidate_entry(entry, response, request) if response.status.not_modified? response end |
#build_cached_response(entry, request) ⇒ HTTP::Response
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Build a response from a cached entry
195 196 197 198 199 200 201 202 203 204 |
# File 'lib/http/features/caching.rb', line 195 def build_cached_response(entry, request) Response.new( status: entry.status, version: entry.version, headers: entry.headers, proxy_headers: entry.proxy_headers, body: entry.body, request: request ) end |
#build_entry(response, body_string) ⇒ Entry
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Build a cache entry from a response
124 125 126 127 128 129 130 131 132 133 134 |
# File 'lib/http/features/caching.rb', line 124 def build_entry(response, body_string) Entry.new( status: response.code, version: response.version, headers: response.headers.dup, proxy_headers: response.proxy_headers, body: body_string, request_uri: response.uri, stored_at: now ) end |
#cacheable_request?(request) ⇒ Boolean
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Check whether this request method is cacheable
139 140 141 |
# File 'lib/http/features/caching.rb', line 139 def cacheable_request?(request) CACHEABLE_METHODS.include?(request.verb) end |
#cacheable_response?(response) ⇒ Boolean
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Check whether this response is cacheable
146 147 148 149 150 151 152 153 154 |
# File 'lib/http/features/caching.rb', line 146 def cacheable_response?(response) return false if response.status < 200 return false if response.status >= 400 directives = parse_cache_control(response.headers) return false if directives.include?("no-store") freshness_info?(response, directives) end |
#freshness_info?(response, directives) ⇒ Boolean
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Whether the response carries enough information to determine freshness
159 160 161 162 163 164 165 |
# File 'lib/http/features/caching.rb', line 159 def freshness_info?(response, directives) return true if directives.any? { |d| d.start_with?("max-age=") } return true if response.headers.include?(Headers::EXPIRES) return true if response.headers.include?(Headers::ETAG) response.headers.include?(Headers::LAST_MODIFIED) end |
#now ⇒ Time
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Current time (extracted for testability)
209 210 211 |
# File 'lib/http/features/caching.rb', line 209 def now Time.now end |
#parse_cache_control(headers) ⇒ Array<String>
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Parse Cache-Control header into a list of directives
170 171 172 |
# File 'lib/http/features/caching.rb', line 170 def parse_cache_control(headers) String(headers[Headers::CACHE_CONTROL]).downcase.split(",").map(&:strip) end |
#revalidate_entry(entry, response, request) ⇒ HTTP::Response
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Revalidate a cached entry with a 304 response
98 99 100 101 102 |
# File 'lib/http/features/caching.rb', line 98 def revalidate_entry(entry, response, request) entry.update_headers!(response.headers) entry.revalidate! build_cached_response(entry, request) end |
#store_and_freeze_response(response) ⇒ HTTP::Response
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Store response in cache and return a new response with eagerly-read body
107 108 109 110 111 112 113 114 115 116 117 118 119 |
# File 'lib/http/features/caching.rb', line 107 def store_and_freeze_response(response) body_string = String(response) store.store(response.request, build_entry(response, body_string)) Response.new( status: response.code, version: response.version, headers: response.headers, proxy_headers: response.proxy_headers, body: body_string, request: response.request ) end |
#wrap_response(response) ⇒ HTTP::Response
Stores cacheable responses in the cache
86 87 88 89 90 91 |
# File 'lib/http/features/caching.rb', line 86 def wrap_response(response) return response unless cacheable_request?(response.request) return response unless cacheable_response?(response) store_and_freeze_response(response) end |