ruby-http-session 1.0.1 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '0787793708be50172480874386353726e9c1ea3c84a78dd3850772d8f5e0a5df'
4
- data.tar.gz: '0519180cc38ad17de9f85cb24758c9e057f6e847868b05203ac4b566eecf9329'
3
+ metadata.gz: cfd1a068f4386cf1a7965a369a01283836a314425141cdfedfdd14edb0330d88
4
+ data.tar.gz: 6efc3597fa28d442fd793a24a9dc9a1020a1effc123bdad55fbd116470f2c641
5
5
  SHA512:
6
- metadata.gz: 734e2a67d0719a07fec84366fbb3bdd1417302a38b3c881db1a832870ecb3ade5006419f49041b4e29e55f8c310e4b77b92e824d9fef1815a20f7bb20bf6ac0e
7
- data.tar.gz: ee8338fc2a6ab4026d41fa9cd8f3e96cb61662d54e5b5c26c9f57df1ca8c9a005989d8529764b5638760723ec8c819e452d13fc24edfd72ec650ec6f6ad385d8
6
+ metadata.gz: 4b03ee19f4e6cbdb1f74ff6046e0f11edae268821ee76408614c505a9adcb8c06614415d0b042acf84db09a833eda46623cb4d1e906bc4f3db3afb0133edae9f
7
+ data.tar.gz: c1fdd8b754c9fa7fbbcb18d4f7383ffee61dcc7a296a5f2c170a700606afcc8e46b92971196dc92994b8f4e34f54db12a46932578095bd77eff78706b00f676d
data/Gemfile CHANGED
@@ -8,6 +8,7 @@ gemspec
8
8
  # optional dependencies
9
9
  gem "activesupport", "~> 7.0"
10
10
  gem "brotli", platforms: :ruby
11
+ gem "sqlite3", platforms: :ruby
11
12
 
12
13
  # rake
13
14
  gem "rake", "~> 13.0"
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- ruby-http-session (1.0.1)
4
+ ruby-http-session (2.1.0)
5
5
  http (~> 5.1, >= 5.1.1)
6
6
 
7
7
  GEM
@@ -125,6 +125,7 @@ GEM
125
125
  thor (~> 1.0)
126
126
  tilt (~> 2.0)
127
127
  yard (~> 0.9, >= 0.9.24)
128
+ sqlite3 (1.6.7-x86_64-linux)
128
129
  standard (1.31.1)
129
130
  language_server-protocol (~> 3.17.0.2)
130
131
  lint_roller (~> 1.0)
@@ -169,6 +170,7 @@ DEPENDENCIES
169
170
  rubocop (~> 1.29)
170
171
  ruby-http-session!
171
172
  solargraph
173
+ sqlite3
172
174
  standard (~> 1.3)
173
175
  timecop
174
176
  vcr
data/README.md CHANGED
@@ -1,6 +1,23 @@
1
1
  # HTTP::Session
2
2
 
3
- HTTP::Session - a session abstraction for [http.rb] in order to support **cookies** and **caching**.
3
+ HTTP::Session - a session abstraction for [http.rb] in order to support **Cookies** and **Caching**.
4
+
5
+ ```ruby
6
+ require "http-session"
7
+ require "logger"
8
+
9
+ http = HTTP.session(
10
+ cookies: true,
11
+ cache: {private: true, store: [:file_store, "./tmp/cache"]},
12
+ persistent: {pools: {"https://httpbin.org" => true}}
13
+ )
14
+ .timeout(8)
15
+ .follow
16
+ .use(logging: {logger: Logger.new($stdout)})
17
+ .freeze
18
+
19
+ http.get("https://httpbin.org/get")
20
+ ```
4
21
 
5
22
 
6
23
  ## Quickstart
@@ -15,81 +32,55 @@ gem 'ruby-http-session', require: "http-session"
15
32
 
16
33
  ### Cookies
17
34
 
18
- The cookies are set automatically each time a request is made:
35
+ Use `cookies: true` to enable this feature. Once enabled, the cookies are
36
+ automatically set each time a request is made.
19
37
 
20
38
  ```ruby
21
39
  require "http-session"
22
40
 
23
41
  http = HTTP.session(cookies: true)
24
- .follow
25
42
  .freeze
26
43
 
27
- r = http.get("https://httpbin.org/cookies/set/mycookies/abc")
44
+ r = http.get("https://httpbin.org/cookies/set/mycookies/abc", follow: true)
28
45
  pp JSON.parse(r.body)["cookies"] # -> {"mycookies"=>"abc"}
29
46
 
30
47
  r = http.get("https://httpbin.org/cookies")
31
48
  pp JSON.parse(r.body)["cookies"] # -> {"mycookies"=>"abc"}
32
-
33
- http.jar.map { |c| pp [c.domain, c.path, c.to_s] } # => ["httpbin.org", "/", "mycookies=abc"]
34
49
  ```
35
50
 
36
51
  ### Caching
37
52
 
38
- When responses can be reused from a cache, taking into account [HTTP RFC 9111] rules for user agents and
39
- shared caches. The following headers are used to determine whether the response is cacheable or not:
40
-
41
- * `Cache-Control` request header
42
- * `no-store`
43
- * `no-cache`
44
- * `Cache-Control` response header
45
- * `no-store`
46
- * `no-cache`
47
- * `private`
48
- * `public`
49
- * `max-age`
50
- * `s-maxage`
51
- * `Etag` & `Last-Modified` response header for conditional requests
52
- * `Vary` response header for content negotiation
53
-
54
- **This takes 60 times to deliver the request to the origin server:**
53
+ Use `cache: true` to enable this feature. Once enabled, the `Cache-Control`
54
+ will be used to determine whether the response is cacheable or not.
55
55
 
56
56
  ```ruby
57
- require "active_support/all"
58
- require "http"
59
-
60
- ActiveSupport::Notifications.subscribe('start_request.http') do |name, start, finish, id, payload|
61
- pp start: start, req: payload[:request].inspect
62
- end
57
+ require "http-session"
63
58
 
64
- http = HTTP
65
- .follow
66
- .timeout(8)
67
- .use(instrumentation: { instrumenter: ActiveSupport::Notifications.instrumenter })
59
+ http = HTTP.session(cache: true)
60
+ .freeze
68
61
 
62
+ # takes only 1 time to deliver the request to the origin server
69
63
  60.times do
70
- http.get("https://cdn.jsdelivr.net/npm/jquery@3.6.4/dist/jquery.min.js", headers: {"Accept-Encoding" => ""})
64
+ http.get("https://cdn.jsdelivr.net/npm/jquery@3.6.4/dist/jquery.min.js")
71
65
  end
72
66
  ```
73
67
 
74
- **This only takes 1 time to deliver the request to the origin server:**
68
+ ### Persistent Connections (Keep-Alive)
69
+
70
+ Use `persistent: true` to enable this feature. Once enabled, the connection pools
71
+ will be used to manage persistent connections.
75
72
 
76
73
  ```ruby
77
- require "active_support/all"
78
74
  require "http-session"
79
75
 
80
- ActiveSupport::Notifications.subscribe('start_request.http') do |name, start, finish, id, payload|
81
- pp start: start, req: payload[:request].inspect
82
- end
83
-
84
- http = HTTP.session(cache: true)
85
- .follow
86
- .timeout(8)
87
- .use(instrumentation: { instrumenter: ActiveSupport::Notifications.instrumenter })
76
+ http = HTTP.session(persistent: true)
88
77
  .freeze
89
78
 
90
- 60.times do
91
- http.get("https://cdn.jsdelivr.net/npm/jquery@3.6.4/dist/jquery.min.js", headers: {"Accept-Encoding" => ""})
92
- end
79
+ http.get("https://httpbin.org/get") # create a persistent connection#1
80
+ http.get("https://httpbin.org/get") # reuse connection#1
81
+
82
+ http.get("https://example.org") # create a persistent connection#2
83
+ http.get("https://example.org") # reuse connection#2
93
84
  ```
94
85
 
95
86
 
@@ -97,29 +88,45 @@ end
97
88
 
98
89
  ### Caching
99
90
 
91
+ When responses can be reused from a cache, taking into account [HTTP RFC 9111] rules for user agents and
92
+ shared caches. The following headers are used to determine whether the response is cacheable or not:
93
+
94
+ * `Cache-Control` request header
95
+ * `no-store`
96
+ * `no-cache`
97
+ * `Cache-Control` response header
98
+ * `no-store`
99
+ * `no-cache`
100
+ * `private`
101
+ * `public`
102
+ * `max-age`
103
+ * `s-maxage`
104
+ * `Etag` & `Last-Modified` response header for conditional requests
105
+ * `Vary` response header for content negotiation
106
+
100
107
  #### Shared Cache
101
108
 
102
109
  A [shared cache] is a cache that stores responses for **reuse by more than one user**; shared caches
103
110
  are usually (but not always) deployed as a part of an intermediary. **This is used by default**.
104
111
 
112
+ **Note**: Responses for requests with **Authorization** header fields will not be stored in a shared
113
+ cache unless explicitly allowed. Read [rfc9111#section-3.5] for more.
114
+
105
115
  ```ruby
106
116
  http = HTTP.session(cache: true) # or HTTP.session(cache: {shared: true})
107
- .follow
108
- .timeout(4)
109
- .use(hsf_auto_inflate: {br: true})
110
117
  .freeze
111
118
 
112
119
  res = http.get("https://cdn.jsdelivr.net/npm/jquery@3.6.4/dist/jquery.min.js")
113
- p "cache-status: #{res.headers["x-httprb-cache-status"]}" # => miss
120
+ pp "Cache-Status: #{res.headers["X-Httprb-Cache-Status"]}" # -> "Cache-Status: MISS"
114
121
 
115
122
  res = http.get("https://cdn.jsdelivr.net/npm/jquery@3.6.4/dist/jquery.min.js")
116
- p "cache-status: #{res.headers["x-httprb-cache-status"]}" # => hit
123
+ pp "Cache-Status: #{res.headers["X-Httprb-Cache-Status"]}" # -> "Cache-Status: HIT"
117
124
 
118
- res = http.get("https://cdn.jsdelivr.net/npm/jquery@3.6.4/dist/jquery.min.js", headers: {"cache-control" => "no-cache"})
119
- p "cache-status: #{res.headers["x-httprb-cache-status"]}" # => revalidated
125
+ res = http.get("https://cdn.jsdelivr.net/npm/jquery@3.6.4/dist/jquery.min.js", headers: {"Cache-Control" => "no-cache"})
126
+ pp "Cache-Status: #{res.headers["X-Httprb-Cache-Status"]}" # -> "Cache-Status: REVALIDATED"
120
127
 
121
- res = http.get("https://cdn.jsdelivr.net/npm/jquery@3.6.4/dist/jquery.min.js", headers: {"cache-control" => "no-store"})
122
- p "cache-status: #{res.headers["x-httprb-cache-status"]}" # => uncacheable
128
+ res = http.get("https://cdn.jsdelivr.net/npm/jquery@3.6.4/dist/jquery.min.js", headers: {"Cache-Control" => "no-store"})
129
+ pp "Cache-Status: #{res.headers["X-Httprb-Cache-Status"]}" # -> "Cache-Status: UNCACHEABLE"
123
130
  ```
124
131
 
125
132
  #### Private Cache
@@ -129,9 +136,6 @@ component of a user agent.
129
136
 
130
137
  ```ruby
131
138
  http = HTTP.session(cache: {private: true})
132
- .follow
133
- .timeout(4)
134
- .use(hsf_auto_inflate: {br: true})
135
139
  .freeze
136
140
  ```
137
141
 
@@ -143,9 +147,6 @@ can use ths `:store` option to set another store, e.g. `ActiveSupport::Cache::Me
143
147
  ```ruby
144
148
  store = ActiveSupport::Cache::MemCacheStore.new("localhost", "server-downstairs.localnetwork:8229")
145
149
  http = HTTP.session(cache: {store: store})
146
- .follow
147
- .timeout(4)
148
- .use(hsf_auto_inflate: {br: true})
149
150
  .freeze
150
151
  ```
151
152
 
@@ -159,14 +160,109 @@ The following value is used in the `X-Httprb-Cache-Status` response header:
159
160
  * **MISS**: not found in cache, served from the origin server
160
161
  * **UNCACHEABLE**: the request can not use cached response
161
162
 
163
+ ### Persistent Connections (Keep-Alive)
164
+
165
+ ```ruby
166
+ http = HTTP.session(persistent: {
167
+ pools: {
168
+ "https://example.org" => false,
169
+ "https://httpbin.org" => {maxsize: 2},
170
+ "*" => true
171
+ }
172
+ }).freeze
173
+
174
+ # match host: "https://example.org"
175
+ # -> create a non-persistent connection
176
+ http.get("https://example.org")
177
+
178
+ # match host: "https://httpbin.org"
179
+ # -> create a connection pool with maxsize 2, and return a persistent connection
180
+ http.get("https://httpbin.org/get")
181
+
182
+ # match host: "*"
183
+ # -> create a connection pool with maxsize 5, and return a persistent connection
184
+ http.get("https://github.com")
185
+ ```
186
+
187
+ ### Thread Safe
188
+
189
+ The following **HTTP::Session** methods are thread-safe:
190
+
191
+ * **head**
192
+ * **get**
193
+ * **post**
194
+ * **put**
195
+ * **delete**
196
+ * **trace**
197
+ * **options**
198
+ * **connect**
199
+ * **patch**
200
+ * **request**
201
+
162
202
  ### HTTP::Features
163
203
 
164
- The following features are available with `http-session`:
204
+ #### logging
205
+
206
+ Log requests and responses.
207
+
208
+ ```ruby
209
+ require "http-session"
210
+ require "logger"
211
+
212
+ http = HTTP.session
213
+ .use(logging: { logger: Logger.new($stdout) })
214
+ .freeze
165
215
 
166
- * [logging]: Log requests and responses.
167
- * [instrumentation]: Instrument requests and responses. Expects an ActiveSupport::Notifications-compatible instrumenter.
168
- * [hsf_auto_inflate]: Simlar to [auto_inflate], used for automatically decompressing the response body.
169
- * etc.
216
+ http.get("https://httpbin.org/get")
217
+ # I, [2023-10-07T13:17:42.208296 #2708620] INFO -- : > GET https://httpbin.org/get
218
+ # D, [2023-10-07T13:17:42.208349 #2708620] DEBUG -- : Connection: close
219
+ # Host: httpbin.org
220
+ # User-Agent: http.rb/5.1.1
221
+ # ...
222
+ ```
223
+
224
+ #### instrumentation
225
+
226
+ Instrument requests and responses. Expects an ActiveSupport::Notifications-compatible instrumenter.
227
+
228
+ ```ruby
229
+ require "http-session"
230
+ require "active_support/all"
231
+
232
+ ActiveSupport::Notifications.subscribe('start_request.http') do |name, start, finish, id, payload|
233
+ pp start: start, req: payload[:request].inspect
234
+ end
235
+
236
+ ActiveSupport::Notifications.subscribe('request.http') do |name, start, finish, id, payload|
237
+ pp start: start, req: payload[:request].inspect, res: payload[:response].inspect
238
+ end
239
+
240
+ http = HTTP.session
241
+ .use(instrumentation: { instrumenter: ActiveSupport::Notifications.instrumenter })
242
+ .freeze
243
+
244
+ http.get("https://httpbin.org/get")
245
+ # {:start=>2023-10-07 13:14:36.953487129 +0800, :req=>"#<HTTP::Request/1.1 GET https://httpbin.org/get>"}
246
+ # {:start=>2023-10-07 13:14:36.954112865 +0800,
247
+ # :req=>"nil",
248
+ # :res=>"#<HTTP::Response/1.1 200 OK {\"Date\"=>\"Sat, 07 Oct 2023 05:14:37 GMT\", \"Content-Type\"=>\"application/json\", \"Content-Length\"=>\"236\", \"Connection\"=>\"close\", \"Server\"=>\"gunicorn/19.9.0\", \"Access-Control-Allow-Origin\"=>\"*\", \"Access-Control-Allow-Credentials\"=>\"true\", \"X-Httprb-Cache-Status\"=>\"MISS\"}>"}
249
+ ```
250
+
251
+ #### hsf_auto_inflate
252
+
253
+ Simlar to `auto_inflate`, used for automatically decompressing the response body.
254
+
255
+ ```ruby
256
+ require "http-session"
257
+ require "brotli"
258
+
259
+ http = HTTP.session
260
+ .use(hsf_auto_inflate: {br: true})
261
+ .freeze
262
+
263
+ res = http.get("https://cdn.jsdelivr.net/npm/jquery@3.6.4/dist/jquery.min.js", headers: {"Accept-Encoding" => "br"})
264
+ pp res.body.to_s # => "/*! jQuery v3.6.4 | ...
265
+ ```
170
266
 
171
267
  ### Intergate with WebMock
172
268
 
@@ -198,10 +294,7 @@ Everyone interacting in the HTTP::Session project's codebases, issue trackers, c
198
294
 
199
295
 
200
296
  [HTTP RFC 9111]:https://datatracker.ietf.org/doc/html/rfc9111/
297
+ [rfc9111#section-3.5]:https://datatracker.ietf.org/doc/html/rfc9111/#section-3.5
201
298
  [shared cache]:https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching#shared_cache
202
299
  [private cache]:https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching#private_caches
203
300
  [http.rb]:https://github.com/httprb/http
204
- [logging]:https://github.com/httprb/http/wiki/Logging-and-Instrumentation#logging
205
- [instrumentation]:https://github.com/httprb/http/wiki/Logging-and-Instrumentation#instrumentation
206
- [auto_inflate]:https://github.com/httprb/http/wiki/Compression#automatic-inflating
207
- [hsf_auto_inflate]:https://github.com/souk4711/http-session/blob/main/lib/http/session/features/auto_inflate.rb
data/http-session.gemspec CHANGED
@@ -8,8 +8,8 @@ Gem::Specification.new do |spec|
8
8
  spec.authors = ["John Doe"]
9
9
  spec.email = ["johndoe@example.com"]
10
10
 
11
- spec.summary = "HTTP::Session - a session abstraction for http.rb in order to support cookies and caching."
12
- spec.description = "HTTP::Session - a session abstraction for http.rb in order to support cookies and caching."
11
+ spec.summary = "HTTP::Session - a session abstraction for http.rb in order to support Cookies and Caching."
12
+ spec.description = "HTTP::Session - a session abstraction for http.rb in order to support Cookies and Caching."
13
13
  spec.homepage = "https://github.com/souk4711/http-session"
14
14
  spec.license = "MIT"
15
15
  spec.required_ruby_version = ">= 2.6.0"
@@ -1,18 +1,33 @@
1
1
  class HTTP::Session
2
2
  class Cache
3
+ extend Forwardable
3
4
  include MonitorMixin
4
5
 
6
+ # @!method enabled?
7
+ # True when it is enabled.
8
+ # @return [Boolean]
9
+ def_delegator :@options, :enabled?
10
+
11
+ # @!method shared_cache?
12
+ # True when it is a shared cache.
13
+ # @return [Boolean]
14
+ def_delegator :@options, :shared_cache?
15
+
16
+ # @!method private_cache?
17
+ # True when it is a private cache.
18
+ # @return [Boolean]
19
+ def_delegator :@options, :private_cache?
20
+
5
21
  # @param [Options::CacheOption] options
6
22
  def initialize(options)
7
23
  super()
8
-
9
24
  @options = options
10
25
  end
11
26
 
12
27
  # Read an entry from cache.
13
28
  #
14
29
  # @param [Request] req
15
- # @return [Entry]
30
+ # @return [nil, Entry]
16
31
  def read(req)
17
32
  synchronize do
18
33
  key = cache_key_for(req)
@@ -37,21 +52,6 @@ class HTTP::Session
37
52
  end
38
53
  end
39
54
 
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
55
  private
56
56
 
57
57
  def entry_matched?(entry, req)
@@ -91,5 +91,10 @@ class HTTP::Session
91
91
  def cache_key_for(req)
92
92
  Digest::SHA256.hexdigest(req.uri)
93
93
  end
94
+
95
+ # Only available when #enbled? has the value true.
96
+ def store
97
+ @options.store
98
+ end
94
99
  end
95
100
  end
@@ -40,6 +40,12 @@ class HTTP::Session
40
40
  raise
41
41
  end
42
42
 
43
+ def close
44
+ @connection&.close
45
+ @connection = nil
46
+ @state = :clean
47
+ end
48
+
43
49
  private
44
50
 
45
51
  def verify_connection!(uri)
@@ -50,12 +56,6 @@ class HTTP::Session
50
56
  close if @state == :dirty
51
57
  end
52
58
 
53
- def close
54
- @connection&.close
55
- @connection = nil
56
- @state = :clean
57
- end
58
-
59
59
  def build_response(req, options)
60
60
  res = HTTP::Response.new(
61
61
  status: @connection.status_code,
@@ -77,7 +77,7 @@ class HTTP::Session
77
77
  end
78
78
 
79
79
  def build_request(verb, uri, opts = {})
80
- opts = @default_options.merge(opts)
80
+ opts = default_options.merge(opts)
81
81
  uri = make_request_uri(uri, opts)
82
82
  headers = make_request_headers(opts)
83
83
  body = make_request_body(opts, headers)
@@ -9,69 +9,93 @@ class HTTP::Session
9
9
  @session = session
10
10
  end
11
11
 
12
+ # Make an HTTP request.
13
+ #
12
14
  # @param verb
13
15
  # @param uri
14
16
  # @param [Hash] opts
17
+ # @param [nil, Context] ctx
15
18
  # @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
19
+ def request(verb, uri, opts, ctx)
20
+ opts = default_options.merge(opts)
21
+ opts = _hs_handle_http_request_options_cookies(uri, opts)
22
+
23
+ req = _hs_build_request(verb, uri, opts, ctx)
24
+ res = _hs_perform(req, opts)
25
+ _hs_cookies_save(res)
26
+
27
+ res
34
28
  end
35
29
 
36
30
  private
37
31
 
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)
32
+ # Add session cookies to the request's :cookies.
33
+ def _hs_handle_http_request_options_cookies(uri, opts)
34
+ cookies = _hs_cookies_load(uri)
35
+ cookies.nil? ? opts : opts.with_cookies(cookies)
46
36
  end
47
37
 
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)
38
+ # Load cookies.
39
+ def _hs_cookies_load(uri)
40
+ return unless @session.cookies_mgr.enabled?
41
+ @session.cookies_mgr.read(uri)
52
42
  end
53
43
 
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
44
+ # Save cookies.
45
+ def _hs_cookies_save(res)
46
+ return unless @session.cookies_mgr.enabled?
47
+ @session.cookies_mgr.write(res)
48
+ end
57
49
 
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
- ))
50
+ # Build a HTTP request.
51
+ def _hs_build_request(verb, uri, opts, ctx)
52
+ return build_request(verb, uri, opts) unless ctx&.follow
53
+ _hs_build_redirection_request(verb, uri, opts, ctx.follow)
62
54
  end
63
55
 
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)
56
+ # Build a HTTP request for redirection.
57
+ def _hs_build_redirection_request(verb, uri, opts, ctx)
58
+ opts = default_options.merge(opts)
59
+ headers = make_request_headers(opts)
60
+ body = make_request_body(opts, headers)
61
+
62
+ # Drop body when non-GET methods changed to GET.
63
+ if ctx.should_drop_body?
64
+ body = nil
65
+ headers.delete(HTTP::Headers::CONTENT_TYPE)
66
+ headers.delete(HTTP::Headers::CONTENT_LENGTH)
67
+ end
68
+
69
+ # Remove Authorization header when rediecting cross site.
70
+ if ctx.cross_origin?
71
+ headers.delete(HTTP::Headers::AUTHORIZATION)
69
72
  end
73
+
74
+ # Build a request.
75
+ req = HTTP::Request.new(
76
+ verb: verb,
77
+ uri: uri,
78
+ uri_normalizer: opts.feature(:normalize_uri)&.normalizer,
79
+ proxy: opts.proxy,
80
+ headers: headers,
81
+ body: body
82
+ )
83
+ HTTP::Session::Request.new(req)
70
84
  end
71
85
 
72
- # Make an HTTP request using cache.
86
+ # Perform a single HTTP request.
87
+ #
88
+ # @param [Request] req
89
+ # @param [HTTP::Options] opts
90
+ # @return [Response]
73
91
  def _hs_perform(req, opts)
74
- if @session.default_options.cache.enabled?
92
+ req = wrap_request(req, opts)
93
+ wrap_response(_hs_perform_no_features(req, opts), opts)
94
+ end
95
+
96
+ # Perform a single HTTP request without any features.
97
+ def _hs_perform_no_features(req, opts)
98
+ if @session.cache_mgr.enabled?
75
99
  req.cacheable? ? _hs_cache_lookup(req, opts) : _hs_cache_pass(req, opts)
76
100
  else
77
101
  _hs_forward(req, opts)
@@ -86,10 +110,10 @@ class HTTP::Session
86
110
  # entry with the backend using conditional GET.
87
111
  # * When no matching cache entry is found, trigger miss processing.
88
112
  def _hs_cache_lookup(req, opts)
89
- entry = @session.cache.read(req)
113
+ entry = @session.cache_mgr.read(req)
90
114
  if entry.nil?
91
115
  _hs_cache_fetch(req, opts)
92
- elsif entry.response.fresh?(shared: @session.cache.shared?) &&
116
+ elsif entry.response.fresh?(shared: @session.cache_mgr.shared_cache?) &&
93
117
  !entry.response.no_cache? &&
94
118
  !req.no_cache?
95
119
  _hs_cache_reuse(req, opts, entry)
@@ -144,14 +168,16 @@ class HTTP::Session
144
168
 
145
169
  # Store the response to cache.
146
170
  def _hs_cache_entry_store(req, res)
147
- if res.cacheable?(shared: @session.cache.shared?, req: req)
148
- @session.cache.write(req, res)
171
+ if res.cacheable?(shared: @session.cache_mgr.shared_cache?, req: req)
172
+ @session.cache_mgr.write(req, res)
149
173
  end
150
174
  end
151
175
 
152
176
  # Delegate the request to the backend and create the response.
153
177
  def _hs_forward(req, opts)
154
- httprb_perform(req, opts)
178
+ res = httprb_perform(req, opts)
179
+ res.flush if default_options.persistent?
180
+ res
155
181
  end
156
182
  end
157
183
  end
@@ -135,8 +135,7 @@ class HTTP::Session
135
135
  private
136
136
 
137
137
  # :nodoc:
138
- def branch(default_options)
139
- raise FrozenError, "can't modify frozen #{self.class.name}" if frozen?
138
+ def branch(_default_options)
140
139
  self
141
140
  end
142
141
  end