ruby-http-session 1.0.1 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile +1 -0
- data/Gemfile.lock +3 -1
- data/README.md +164 -71
- data/http-session.gemspec +2 -2
- data/lib/http/session/cache.rb +22 -17
- data/lib/http/session/client/perform.rb +7 -7
- data/lib/http/session/client.rb +75 -49
- data/lib/http/session/configurable.rb +1 -2
- data/lib/http/session/connection_pool.rb +88 -0
- data/lib/http/session/context/follow_context.rb +24 -0
- data/lib/http/session/context.rb +13 -0
- data/lib/http/session/cookies.rb +55 -0
- data/lib/http/session/exceptions.rb +7 -0
- data/lib/http/session/features/auto_inflate.rb +0 -4
- data/lib/http/session/features.rb +5 -0
- data/lib/http/session/options/cache_option.rb +20 -26
- data/lib/http/session/options/cookies_option.rb +31 -11
- data/lib/http/session/options/optionable.rb +21 -0
- data/lib/http/session/options/persistent_option.rb +35 -0
- data/lib/http/session/options.rb +15 -0
- data/lib/http/session/pool_manager.rb +67 -0
- data/lib/http/session/redirector.rb +89 -0
- data/lib/http/session/response/string_body.rb +0 -2
- data/lib/http/session/version.rb +1 -1
- data/lib/http/session.rb +45 -48
- data/lib/http-session.rb +1 -0
- metadata +15 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cfd1a068f4386cf1a7965a369a01283836a314425141cdfedfdd14edb0330d88
|
4
|
+
data.tar.gz: 6efc3597fa28d442fd793a24a9dc9a1020a1effc123bdad55fbd116470f2c641
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4b03ee19f4e6cbdb1f74ff6046e0f11edae268821ee76408614c505a9adcb8c06614415d0b042acf84db09a833eda46623cb4d1e906bc4f3db3afb0133edae9f
|
7
|
+
data.tar.gz: c1fdd8b754c9fa7fbbcb18d4f7383ffee61dcc7a296a5f2c170a700606afcc8e46b92971196dc92994b8f4e34f54db12a46932578095bd77eff78706b00f676d
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
ruby-http-session (1.0
|
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 **
|
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
|
-
|
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
|
-
|
39
|
-
|
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 "
|
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
|
-
.
|
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"
|
64
|
+
http.get("https://cdn.jsdelivr.net/npm/jquery@3.6.4/dist/jquery.min.js")
|
71
65
|
end
|
72
66
|
```
|
73
67
|
|
74
|
-
|
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
|
-
|
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
|
-
|
91
|
-
|
92
|
-
|
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
|
-
|
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
|
-
|
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: {"
|
119
|
-
|
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: {"
|
122
|
-
|
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
|
-
|
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
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
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
|
12
|
-
spec.description = "HTTP::Session - a session abstraction for http.rb in order to support
|
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"
|
data/lib/http/session/cache.rb
CHANGED
@@ -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 =
|
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)
|
data/lib/http/session/client.rb
CHANGED
@@ -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
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
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
|
-
#
|
39
|
-
|
40
|
-
|
41
|
-
|
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
|
-
#
|
49
|
-
def
|
50
|
-
return
|
51
|
-
|
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
|
-
#
|
55
|
-
def
|
56
|
-
return
|
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
|
-
|
59
|
-
|
60
|
-
|
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
|
-
#
|
65
|
-
def
|
66
|
-
|
67
|
-
|
68
|
-
|
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
|
-
#
|
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
|
-
|
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.
|
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.
|
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.
|
148
|
-
@session.
|
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
|