wreq 1.2.4 → 1.2.5
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/Cargo.lock +3 -7
- data/Cargo.toml +6 -1
- data/crates/wreq-util/.github/FUNDING.yml +15 -0
- data/crates/wreq-util/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
- data/crates/wreq-util/.github/ISSUE_TEMPLATE/custom.md +10 -0
- data/crates/wreq-util/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- data/crates/wreq-util/.github/dependabot.yml +22 -0
- data/crates/wreq-util/.github/workflows/ci.yml +82 -0
- data/crates/wreq-util/.github/workflows/release-plz.yml +52 -0
- data/crates/wreq-util/.gitignore +24 -0
- data/crates/wreq-util/CHANGELOG.md +74 -0
- data/crates/wreq-util/Cargo.toml +94 -0
- data/crates/wreq-util/LICENSE +201 -0
- data/crates/wreq-util/README.md +62 -0
- data/crates/wreq-util/examples/emulate.rs +39 -0
- data/crates/wreq-util/examples/tower_delay.rs +191 -0
- data/crates/wreq-util/release-plz.toml +2 -0
- data/crates/wreq-util/rustfmt.toml +5 -0
- data/crates/wreq-util/src/emulate/compress.rs +85 -0
- data/crates/wreq-util/src/emulate/macros.rs +370 -0
- data/crates/wreq-util/src/emulate/profile/chrome/header.rs +59 -0
- data/crates/wreq-util/src/emulate/profile/chrome/http2.rs +65 -0
- data/crates/wreq-util/src/emulate/profile/chrome/tls.rs +153 -0
- data/crates/wreq-util/src/emulate/profile/chrome.rs +2028 -0
- data/crates/wreq-util/src/emulate/profile/firefox/header.rs +21 -0
- data/crates/wreq-util/src/emulate/profile/firefox/http2.rs +113 -0
- data/crates/wreq-util/src/emulate/profile/firefox/tls.rs +253 -0
- data/crates/wreq-util/src/emulate/profile/firefox.rs +518 -0
- data/crates/wreq-util/src/emulate/profile/okhttp.rs +241 -0
- data/crates/wreq-util/src/emulate/profile/opera/header.rs +30 -0
- data/crates/wreq-util/src/emulate/profile/opera/http2.rs +50 -0
- data/crates/wreq-util/src/emulate/profile/opera/tls.rs +97 -0
- data/crates/wreq-util/src/emulate/profile/opera.rs +299 -0
- data/crates/wreq-util/src/emulate/profile/safari/header.rs +59 -0
- data/crates/wreq-util/src/emulate/profile/safari/http2.rs +116 -0
- data/crates/wreq-util/src/emulate/profile/safari/tls.rs +177 -0
- data/crates/wreq-util/src/emulate/profile/safari.rs +222 -0
- data/crates/wreq-util/src/emulate/profile.rs +47 -0
- data/crates/wreq-util/src/emulate.rs +369 -0
- data/crates/wreq-util/src/lib.rs +14 -0
- data/crates/wreq-util/src/rand.rs +24 -0
- data/crates/wreq-util/src/tower/delay/future.rs +43 -0
- data/crates/wreq-util/src/tower/delay/layer.rs +201 -0
- data/crates/wreq-util/src/tower/delay/service.rs +190 -0
- data/crates/wreq-util/src/tower/delay.rs +98 -0
- data/crates/wreq-util/src/tower.rs +7 -0
- data/crates/wreq-util/tests/client.rs +51 -0
- data/crates/wreq-util/tests/emulate_chrome.rs +270 -0
- data/crates/wreq-util/tests/emulate_firefox.rs +92 -0
- data/crates/wreq-util/tests/emulate_okhttp.rs +46 -0
- data/crates/wreq-util/tests/emulate_opera.rs +113 -0
- data/crates/wreq-util/tests/emulate_safari.rs +151 -0
- data/crates/wreq-util/tests/support/mod.rs +55 -0
- data/crates/wreq-util/tests/support/server.rs +232 -0
- data/examples/emulate_request.rb +1 -1
- data/lib/wreq.rb +20 -18
- data/lib/wreq_ruby/body.rb +3 -0
- data/lib/wreq_ruby/client.rb +18 -18
- data/lib/wreq_ruby/cookie.rb +2 -2
- data/lib/wreq_ruby/emulate.rb +3 -1
- data/lib/wreq_ruby/header.rb +205 -114
- data/lib/wreq_ruby/response.rb +28 -3
- data/src/emulate.rs +1 -0
- data/src/error.rs +2 -6
- data/src/header/helper.rs +112 -0
- data/src/header.rs +198 -133
- data/test/header_test.rb +228 -192
- data/test/stream_test.rb +36 -0
- metadata +54 -1
data/lib/wreq_ruby/emulate.rb
CHANGED
|
@@ -58,6 +58,7 @@ module Wreq
|
|
|
58
58
|
Chrome147 = nil
|
|
59
59
|
Chrome148 = nil
|
|
60
60
|
Chrome149 = nil
|
|
61
|
+
Chrome150 = nil
|
|
61
62
|
|
|
62
63
|
Edge101 = nil
|
|
63
64
|
Edge122 = nil
|
|
@@ -154,7 +155,7 @@ module Wreq
|
|
|
154
155
|
Opera128 = nil
|
|
155
156
|
Opera129 = nil
|
|
156
157
|
Opera130 = nil
|
|
157
|
-
Opera131 =
|
|
158
|
+
Opera131 = nil
|
|
158
159
|
end
|
|
159
160
|
|
|
160
161
|
unless method_defined?(:to_s)
|
|
@@ -225,6 +226,7 @@ module Wreq
|
|
|
225
226
|
# @param platform [Wreq::Platform, nil] Operating system platform to emulate
|
|
226
227
|
# @param http2 [Boolean] Whether HTTP/2 support is enabled
|
|
227
228
|
# @param headers [Boolean] Whether default emulation headers are enabled
|
|
229
|
+
# @return [Wreq::Emulation] Configured emulation settings
|
|
228
230
|
def self.new(profile: nil, platform: nil, http2: true, headers: true)
|
|
229
231
|
end
|
|
230
232
|
end
|
data/lib/wreq_ruby/header.rb
CHANGED
|
@@ -2,202 +2,293 @@
|
|
|
2
2
|
|
|
3
3
|
unless defined?(Wreq)
|
|
4
4
|
module Wreq
|
|
5
|
-
# HTTP headers
|
|
5
|
+
# A mutable, case-insensitive collection of HTTP headers.
|
|
6
6
|
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
7
|
+
# Header names are normalized by the native header map and lookups are
|
|
8
|
+
# case-insensitive. Use the `orig_headers` request option when exact wire
|
|
9
|
+
# casing or header order is required. Duplicate values are stored as
|
|
10
|
+
# separate header occurrences.
|
|
9
11
|
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
+
# @example Build and query headers
|
|
13
|
+
# headers = Wreq::Headers.new(
|
|
14
|
+
# "Accept" => ["application/json", "text/plain"],
|
|
15
|
+
# content_type: "application/json"
|
|
16
|
+
# )
|
|
17
|
+
# headers["accept"] # => ["application/json", "text/plain"]
|
|
18
|
+
# headers[:content_type] # => "application/json"
|
|
12
19
|
#
|
|
13
|
-
# @example
|
|
14
|
-
# response = Wreq.get("https://example.com")
|
|
15
|
-
# headers = response.headers
|
|
16
|
-
# content_type = headers["Content-Type"]
|
|
17
|
-
# content_type = headers.get("content-type") # Same, case-insensitive
|
|
18
|
-
#
|
|
19
|
-
# @example Getting all values for a header
|
|
20
|
-
# accept_values = headers.get_all("Accept")
|
|
21
|
-
# # => ["application/json", "text/html"]
|
|
22
|
-
#
|
|
23
|
-
# @example Modifying headers
|
|
24
|
-
# headers.set("X-Custom-Header", "value")
|
|
25
|
-
# headers["Authorization"] = "Bearer token"
|
|
26
|
-
# headers.append("Accept", "application/xml")
|
|
27
|
-
#
|
|
28
|
-
# @example Iterating headers
|
|
20
|
+
# @example Iterate over every occurrence
|
|
29
21
|
# headers.each do |name, value|
|
|
30
22
|
# puts "#{name}: #{value}"
|
|
31
23
|
# end
|
|
32
|
-
#
|
|
33
|
-
# @example Converting to hash
|
|
34
|
-
# hash = headers.to_h
|
|
35
|
-
# hash["content-type"] # => "text/html"
|
|
36
24
|
class Headers
|
|
37
|
-
|
|
25
|
+
include Enumerable
|
|
26
|
+
|
|
27
|
+
# Create an empty collection or copy header pairs from a source.
|
|
38
28
|
#
|
|
39
|
-
# @
|
|
29
|
+
# @param source [Hash, Wreq::Headers, Enumerable] A hash, another
|
|
30
|
+
# headers collection, or an enumerable that yields name-value pairs.
|
|
31
|
+
# Omit this argument to create an empty collection.
|
|
32
|
+
# @return [Wreq::Headers]
|
|
33
|
+
# @raise [Wreq::BuilderError] if the source does not contain valid pairs
|
|
40
34
|
# @example
|
|
41
|
-
#
|
|
42
|
-
#
|
|
43
|
-
|
|
35
|
+
# Wreq::Headers.new
|
|
36
|
+
# Wreq::Headers.new("Accept" => "application/json")
|
|
37
|
+
# Wreq::Headers.new([[:content_type, "application/json"]])
|
|
38
|
+
def self.new(*args)
|
|
44
39
|
end
|
|
45
40
|
|
|
46
|
-
#
|
|
41
|
+
# Return the first value for a header.
|
|
47
42
|
#
|
|
48
|
-
#
|
|
49
|
-
#
|
|
50
|
-
# @param name [String] Header name (case-insensitive)
|
|
51
|
-
# @return [String, nil] Header value, or nil if not found
|
|
43
|
+
# @param name [String, Symbol] Header name
|
|
44
|
+
# @return [String, nil] The first value, or nil when the name is missing
|
|
52
45
|
# @example
|
|
53
|
-
# headers.get("
|
|
54
|
-
# headers.get(
|
|
55
|
-
# headers.get("X-Nonexistent") # => nil
|
|
46
|
+
# headers.get("content-type") # => "application/json"
|
|
47
|
+
# headers.get(:missing) # => nil
|
|
56
48
|
def get(name)
|
|
57
49
|
end
|
|
58
50
|
|
|
59
|
-
#
|
|
51
|
+
# Return a header using collection-style value semantics.
|
|
52
|
+
#
|
|
53
|
+
# A missing name returns nil, one occurrence returns a String, and
|
|
54
|
+
# multiple occurrences return an Array<String>.
|
|
60
55
|
#
|
|
61
|
-
#
|
|
56
|
+
# @param name [String, Symbol] Header name
|
|
57
|
+
# @return [String, Array<String>, nil]
|
|
58
|
+
# @example
|
|
59
|
+
# headers["accept"] # => "application/json"
|
|
60
|
+
# headers["set-cookie"] # => ["a=1", "b=2"]
|
|
61
|
+
def [](name)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Return every value for a header.
|
|
62
65
|
#
|
|
63
|
-
# @param name [String] Header name
|
|
64
|
-
# @return [Array<String>]
|
|
66
|
+
# @param name [String, Symbol] Header name
|
|
67
|
+
# @return [Array<String>] Values in insertion order, or an empty array
|
|
65
68
|
# @example
|
|
66
|
-
# headers.get_all("
|
|
67
|
-
# # => [
|
|
68
|
-
# headers.get_all("X-Nonexistent") # => []
|
|
69
|
+
# headers.get_all("set-cookie") # => ["a=1", "b=2"]
|
|
70
|
+
# headers.get_all(:missing) # => []
|
|
69
71
|
def get_all(name)
|
|
70
72
|
end
|
|
71
73
|
|
|
72
|
-
# Set
|
|
74
|
+
# Set one or more values, replacing every existing occurrence.
|
|
75
|
+
#
|
|
76
|
+
# Array values are stored as separate occurrences and are not joined. An
|
|
77
|
+
# empty Array removes the header.
|
|
73
78
|
#
|
|
74
|
-
# @param name [String] Header name
|
|
75
|
-
# @param value [String] Header value
|
|
79
|
+
# @param name [String, Symbol] Header name
|
|
80
|
+
# @param value [String, Array<String>] Header value or values
|
|
76
81
|
# @return [void]
|
|
77
|
-
# @raise [Wreq::BuilderError] if name or value
|
|
82
|
+
# @raise [Wreq::BuilderError] if a name or value is invalid
|
|
78
83
|
# @example
|
|
79
|
-
# headers.set("
|
|
80
|
-
# headers.set("X-Custom-Header", "my-value")
|
|
84
|
+
# headers.set("Accept", ["application/json", "text/plain"])
|
|
81
85
|
def set(name, value)
|
|
82
86
|
end
|
|
83
87
|
|
|
84
|
-
#
|
|
88
|
+
# Set one or more values and return the assigned value.
|
|
85
89
|
#
|
|
86
|
-
#
|
|
87
|
-
#
|
|
90
|
+
# @param name [String, Symbol] Header name
|
|
91
|
+
# @param value [String, Array<String>] Header value or values
|
|
92
|
+
# @return [String, Array<String>] The assigned value
|
|
93
|
+
# @example
|
|
94
|
+
# headers[:content_type] = "application/json"
|
|
95
|
+
def []=(name, value)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Append one or more values without replacing existing occurrences.
|
|
88
99
|
#
|
|
89
|
-
# @param name [String] Header name
|
|
90
|
-
# @param value [String] Header value
|
|
100
|
+
# @param name [String, Symbol] Header name
|
|
101
|
+
# @param value [String, Array<String>] Header value or values
|
|
91
102
|
# @return [void]
|
|
92
|
-
# @raise [Wreq::BuilderError] if name or value
|
|
103
|
+
# @raise [Wreq::BuilderError] if a name or value is invalid
|
|
93
104
|
# @example
|
|
94
|
-
# headers.
|
|
95
|
-
# headers.append("Accept", "text/html")
|
|
96
|
-
# headers.get_all("Accept") # => ["application/json", "text/html"]
|
|
105
|
+
# headers.append("Set-Cookie", ["a=1", "b=2"])
|
|
97
106
|
def append(name, value)
|
|
98
107
|
end
|
|
99
108
|
|
|
100
|
-
#
|
|
109
|
+
# Return a header value, a fallback, or the result of a block.
|
|
101
110
|
#
|
|
102
|
-
# @param name [String] Header name
|
|
103
|
-
# @
|
|
111
|
+
# @param name [String, Symbol] Header name
|
|
112
|
+
# @param default [Object] Optional fallback for a missing name
|
|
113
|
+
# @yieldparam name [String, Symbol] The missing name
|
|
114
|
+
# @return [String, Array<String>, Object]
|
|
115
|
+
# @raise [KeyError] if the name is missing and no fallback is provided
|
|
104
116
|
# @example
|
|
105
|
-
# headers.
|
|
106
|
-
# headers.
|
|
107
|
-
def
|
|
117
|
+
# headers.fetch("accept", "*/*")
|
|
118
|
+
# headers.fetch(:missing) { |name| "missing: #{name}" }
|
|
119
|
+
def fetch(name, default = nil)
|
|
108
120
|
end
|
|
109
121
|
|
|
110
|
-
#
|
|
122
|
+
# Remove every occurrence for a header.
|
|
111
123
|
#
|
|
112
|
-
# @param name [String] Header name
|
|
113
|
-
# @return [
|
|
124
|
+
# @param name [String, Symbol] Header name
|
|
125
|
+
# @return [String, nil] The first removed value, or nil when missing
|
|
114
126
|
# @example
|
|
115
|
-
# headers.
|
|
116
|
-
|
|
127
|
+
# headers.remove("authorization") # => "Bearer token"
|
|
128
|
+
def remove(name)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Remove every occurrence for a header. Alias for {#remove}.
|
|
132
|
+
#
|
|
133
|
+
# @param name [String, Symbol] Header name
|
|
134
|
+
# @return [String, nil] The first removed value, or nil when missing
|
|
135
|
+
def delete(name)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Check whether a header exists.
|
|
139
|
+
#
|
|
140
|
+
# @param name [String, Symbol] Header name
|
|
141
|
+
# @return [Boolean]
|
|
117
142
|
def contains?(name)
|
|
118
143
|
end
|
|
119
144
|
|
|
120
|
-
# Check
|
|
145
|
+
# Check whether a header exists. Alias for {#contains?}.
|
|
121
146
|
#
|
|
122
|
-
# @param name [String] Header name
|
|
123
|
-
# @return [Boolean]
|
|
124
|
-
# @example
|
|
125
|
-
# headers.key?("Accept") # => true
|
|
147
|
+
# @param name [String, Symbol] Header name
|
|
148
|
+
# @return [Boolean]
|
|
126
149
|
def key?(name)
|
|
127
150
|
end
|
|
128
151
|
|
|
129
|
-
#
|
|
152
|
+
# Return the number of header occurrences.
|
|
130
153
|
#
|
|
131
|
-
#
|
|
132
|
-
#
|
|
133
|
-
#
|
|
154
|
+
# This can be greater than `keys.length` when a name has multiple values.
|
|
155
|
+
#
|
|
156
|
+
# @return [Integer]
|
|
134
157
|
def length
|
|
135
158
|
end
|
|
136
159
|
|
|
137
|
-
#
|
|
160
|
+
# Return the number of header occurrences. Alias for {#length}.
|
|
138
161
|
#
|
|
139
|
-
# @return [
|
|
140
|
-
|
|
141
|
-
|
|
162
|
+
# @return [Integer]
|
|
163
|
+
def size
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Check whether the collection has no header occurrences.
|
|
167
|
+
#
|
|
168
|
+
# @return [Boolean]
|
|
142
169
|
def empty?
|
|
143
170
|
end
|
|
144
171
|
|
|
145
|
-
# Remove
|
|
172
|
+
# Remove every header occurrence.
|
|
146
173
|
#
|
|
147
|
-
# @return [
|
|
148
|
-
# @example
|
|
149
|
-
# headers.clear
|
|
150
|
-
# headers.empty? # => true
|
|
174
|
+
# @return [Wreq::Headers] self
|
|
151
175
|
def clear
|
|
152
176
|
end
|
|
153
177
|
|
|
154
|
-
#
|
|
178
|
+
# Return each unique header name.
|
|
155
179
|
#
|
|
156
|
-
# @return [Array<String>]
|
|
157
|
-
# @example
|
|
158
|
-
# headers.keys
|
|
159
|
-
# # => ["content-type", "accept", "user-agent", "authorization"]
|
|
180
|
+
# @return [Array<String>]
|
|
160
181
|
def keys
|
|
161
182
|
end
|
|
162
183
|
|
|
163
|
-
#
|
|
164
|
-
#
|
|
165
|
-
# Returns one value per header (the first if multiple values exist).
|
|
184
|
+
# Return every header value.
|
|
166
185
|
#
|
|
167
|
-
# @return [Array<String>]
|
|
168
|
-
# @example
|
|
169
|
-
# headers.values
|
|
170
|
-
# # => ["application/json", "text/html", "Mozilla/5.0", "Bearer token"]
|
|
186
|
+
# @return [Array<String>]
|
|
171
187
|
def values
|
|
172
188
|
end
|
|
173
189
|
|
|
174
|
-
# Iterate over
|
|
175
|
-
#
|
|
176
|
-
# Yields each header name and value pair. If a header has multiple values,
|
|
177
|
-
# only the first is yielded.
|
|
190
|
+
# Iterate over every header occurrence.
|
|
178
191
|
#
|
|
179
|
-
# @yieldparam name [String]
|
|
192
|
+
# @yieldparam name [String] Normalized lowercase header name
|
|
180
193
|
# @yieldparam value [String] Header value
|
|
181
|
-
# @return [Enumerator,
|
|
182
|
-
#
|
|
183
|
-
#
|
|
184
|
-
#
|
|
185
|
-
# end
|
|
186
|
-
# @example Without block
|
|
187
|
-
# enum = headers.each
|
|
188
|
-
# enum.to_a # => [["content-type", "text/html"], ...]
|
|
194
|
+
# @return [Enumerator, Wreq::Headers] An Enumerator without a block,
|
|
195
|
+
# otherwise self
|
|
196
|
+
# @example
|
|
197
|
+
# headers.each.to_a
|
|
189
198
|
def each
|
|
190
199
|
end
|
|
191
200
|
|
|
201
|
+
# Convert every occurrence to name-value pairs.
|
|
202
|
+
#
|
|
203
|
+
# @return [Array<Array(String, String)>]
|
|
204
|
+
# @example
|
|
205
|
+
# headers.to_a # => [["accept", "application/json"], ...]
|
|
206
|
+
def to_a
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Convert unique names to a Hash.
|
|
210
|
+
#
|
|
211
|
+
# Hash values use the same nil, String, or Array shape as {#[]}.
|
|
212
|
+
#
|
|
213
|
+
# @return [Hash{String => String, Array<String>}]
|
|
214
|
+
# @example
|
|
215
|
+
# headers.to_h # => {"accept" => "application/json"}
|
|
216
|
+
def to_h
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# Convert unique names to a Hash. Alias for {#to_h}.
|
|
220
|
+
#
|
|
221
|
+
# @return [Hash{String => String, Array<String>}]
|
|
222
|
+
def to_hash
|
|
223
|
+
end
|
|
224
|
+
|
|
192
225
|
# Convert headers to a string representation.
|
|
226
|
+
#
|
|
227
|
+
# @return [String] String representation of the headers
|
|
193
228
|
def to_s
|
|
194
229
|
end
|
|
230
|
+
|
|
231
|
+
# Return a compact representation for debugging.
|
|
232
|
+
#
|
|
233
|
+
# @return [String]
|
|
234
|
+
# @example
|
|
235
|
+
# headers.inspect # => "#<Wreq::Headers [2 headers]>"
|
|
236
|
+
def inspect
|
|
237
|
+
end
|
|
195
238
|
end
|
|
196
239
|
end
|
|
197
240
|
end
|
|
198
241
|
|
|
242
|
+
# ======================== Ruby API Extensions ========================
|
|
243
|
+
|
|
199
244
|
module Wreq
|
|
200
245
|
class Headers
|
|
246
|
+
FETCH_UNDEFINED = Object.new.freeze
|
|
247
|
+
private_constant :FETCH_UNDEFINED
|
|
248
|
+
|
|
249
|
+
alias delete remove
|
|
250
|
+
alias size length
|
|
251
|
+
|
|
252
|
+
# Return a header value, a fallback, or the result of a block.
|
|
253
|
+
#
|
|
254
|
+
# The block takes precedence when both a default and block are provided.
|
|
255
|
+
#
|
|
256
|
+
# @param name [String, Symbol] Header name
|
|
257
|
+
# @param default [Object] Optional fallback for a missing name
|
|
258
|
+
# @yieldparam name [String, Symbol] The missing name
|
|
259
|
+
# @return [String, Array<String>, Object]
|
|
260
|
+
# @raise [KeyError] if the name is missing and no fallback is provided
|
|
261
|
+
def fetch(name, default = FETCH_UNDEFINED)
|
|
262
|
+
value = self[name]
|
|
263
|
+
return value unless value.nil?
|
|
264
|
+
return yield(name) if block_given?
|
|
265
|
+
return default unless default.equal?(FETCH_UNDEFINED)
|
|
266
|
+
|
|
267
|
+
raise KeyError, "key not found: #{name.inspect}"
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# Convert every header occurrence to a name-value pair.
|
|
271
|
+
#
|
|
272
|
+
# @return [Array<Array(String, String)>]
|
|
273
|
+
def to_a
|
|
274
|
+
each.to_a
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
# Convert unique normalized names to a Hash.
|
|
278
|
+
#
|
|
279
|
+
# A name with one occurrence maps to a String, while multiple occurrences
|
|
280
|
+
# map to an Array<String>.
|
|
281
|
+
#
|
|
282
|
+
# @return [Hash{String => String, Array<String>}]
|
|
283
|
+
def to_h
|
|
284
|
+
keys.to_h { |name| [name, self[name]] }
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
alias to_hash to_h
|
|
288
|
+
|
|
289
|
+
# Return a compact representation for debugging.
|
|
290
|
+
#
|
|
291
|
+
# @return [String]
|
|
201
292
|
def inspect
|
|
202
293
|
"#<Wreq::Headers [#{length} headers]>"
|
|
203
294
|
end
|
data/lib/wreq_ruby/response.rb
CHANGED
|
@@ -64,6 +64,19 @@ unless defined?(Wreq)
|
|
|
64
64
|
def content_length
|
|
65
65
|
end
|
|
66
66
|
|
|
67
|
+
# Get the response headers.
|
|
68
|
+
#
|
|
69
|
+
# Header names are case-insensitive. Use {Wreq::Headers#get_all} to get
|
|
70
|
+
# every value when a header appears more than once. Each call returns a
|
|
71
|
+
# fresh, mutable snapshot. Changing that snapshot does not change the
|
|
72
|
+
# response or a later snapshot, and object identity is not guaranteed.
|
|
73
|
+
#
|
|
74
|
+
# @return [Wreq::Headers] Response headers
|
|
75
|
+
# @example
|
|
76
|
+
# response.headers.get("content-type") # => "application/json"
|
|
77
|
+
def headers
|
|
78
|
+
end
|
|
79
|
+
|
|
67
80
|
# Get the local socket address.
|
|
68
81
|
#
|
|
69
82
|
# @return [String, nil] Local address (e.g., "127.0.0.1:54321"), or nil
|
|
@@ -80,6 +93,18 @@ unless defined?(Wreq)
|
|
|
80
93
|
def remote_addr
|
|
81
94
|
end
|
|
82
95
|
|
|
96
|
+
# Get cookies parsed from the response's `Set-Cookie` headers.
|
|
97
|
+
#
|
|
98
|
+
# Invalid `Set-Cookie` values are skipped.
|
|
99
|
+
#
|
|
100
|
+
# @return [Array<Wreq::Cookie>] Parsed response cookies
|
|
101
|
+
# @example
|
|
102
|
+
# response.cookies.each do |cookie|
|
|
103
|
+
# puts "#{cookie.name}=#{cookie.value}"
|
|
104
|
+
# end
|
|
105
|
+
def cookies
|
|
106
|
+
end
|
|
107
|
+
|
|
83
108
|
# Get the response bytes as a binary string.
|
|
84
109
|
# @return [String] Response body as binary data
|
|
85
110
|
# @example
|
|
@@ -91,13 +116,13 @@ unless defined?(Wreq)
|
|
|
91
116
|
# Get the response body as text with a specific charset.
|
|
92
117
|
# This method allows you to specify a default encoding
|
|
93
118
|
# to use when decoding the response body.
|
|
94
|
-
#
|
|
95
|
-
#
|
|
119
|
+
# @param default_encoding [String] Default encoding to use (e.g., "UTF-8")
|
|
120
|
+
# @return [String] Response body decoded as text using the specified encoding
|
|
96
121
|
# @example
|
|
97
122
|
# html = response.text("ISO-8859-1")
|
|
98
123
|
# puts html
|
|
99
124
|
# @raise [Wreq::DecodingError] if body cannot be decoded with the specified encoding
|
|
100
|
-
def text(default_encoding
|
|
125
|
+
def text(default_encoding = "UTF-8")
|
|
101
126
|
end
|
|
102
127
|
|
|
103
128
|
# Parse the response body as JSON.
|
data/src/emulate.rs
CHANGED
data/src/error.rs
CHANGED
|
@@ -72,17 +72,14 @@ define_exception!(DECODING_ERROR, "DecodingError", exception_runtime_error);
|
|
|
72
72
|
// Configuration and builder errors
|
|
73
73
|
define_exception!(BUILDER_ERROR, "BuilderError", exception_runtime_error);
|
|
74
74
|
|
|
75
|
-
// Thread interruption error
|
|
76
|
-
define_exception!(INTERRUPT_ERROR, "InterruptError", exception_interrupt);
|
|
77
|
-
|
|
78
75
|
/// Memory error constant
|
|
79
76
|
pub fn memory_error() -> MagnusError {
|
|
80
77
|
MagnusError::new(ruby!().get_inner(&MEMORY), RACE_CONDITION_ERROR_MSG)
|
|
81
78
|
}
|
|
82
79
|
|
|
83
|
-
///
|
|
80
|
+
/// Create a Ruby thread interruption error.
|
|
84
81
|
pub fn interrupt_error() -> MagnusError {
|
|
85
|
-
MagnusError::new(ruby!().
|
|
82
|
+
MagnusError::new(ruby!().exception_interrupt(), "request interrupted")
|
|
86
83
|
}
|
|
87
84
|
|
|
88
85
|
/// LocalJumpError for methods that require a Ruby block.
|
|
@@ -159,5 +156,4 @@ pub fn include(ruby: &Ruby) {
|
|
|
159
156
|
Lazy::force(&BODY_ERROR, ruby);
|
|
160
157
|
Lazy::force(&DECODING_ERROR, ruby);
|
|
161
158
|
Lazy::force(&BUILDER_ERROR, ruby);
|
|
162
|
-
Lazy::force(&INTERRUPT_ERROR, ruby);
|
|
163
159
|
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
//! Ruby value conversion helpers for `Wreq::Headers`.
|
|
2
|
+
|
|
3
|
+
use bytes::Bytes;
|
|
4
|
+
use http::{HeaderName, HeaderValue};
|
|
5
|
+
use magnus::{Error, RArray, RString, Symbol, TryConvert, Value, prelude::*, typed_data::Obj};
|
|
6
|
+
|
|
7
|
+
use crate::error::{
|
|
8
|
+
header_name_error_to_magnus, header_value_error_to_magnus, type_value_error_to_magnus,
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
use super::Headers;
|
|
12
|
+
|
|
13
|
+
/// Maximum number of field-value occurrences supported by `HeaderMap`.
|
|
14
|
+
const MAX_HEADER_ENTRIES: usize = 1 << 15;
|
|
15
|
+
|
|
16
|
+
/// Build a header collection from a Ruby source object.
|
|
17
|
+
///
|
|
18
|
+
/// Accepts another `Wreq::Headers`, a Hash, or any object whose `to_a` result
|
|
19
|
+
/// contains two-element name-value pairs. Array values are delegated to
|
|
20
|
+
/// [`Headers::append`] so each value remains a separate occurrence.
|
|
21
|
+
pub(super) fn from_source(source: Value) -> Result<Headers, Error> {
|
|
22
|
+
if let Ok(headers) = Obj::<Headers>::try_convert(source) {
|
|
23
|
+
return Ok((*headers).clone());
|
|
24
|
+
}
|
|
25
|
+
if !source.respond_to("to_a", false)? {
|
|
26
|
+
return Err(type_value_error_to_magnus(
|
|
27
|
+
"Expected Headers, a Hash, or an enumerable of pairs",
|
|
28
|
+
));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let pairs: RArray = source.funcall_public("to_a", ())?;
|
|
32
|
+
let headers = Headers::default();
|
|
33
|
+
for pair in pairs {
|
|
34
|
+
let pair = RArray::try_convert(pair)
|
|
35
|
+
.map_err(|_| type_value_error_to_magnus("Expected each header entry to be a pair"))?;
|
|
36
|
+
if pair.len() != 2 {
|
|
37
|
+
return Err(type_value_error_to_magnus(
|
|
38
|
+
"Expected each header entry to contain a name and value",
|
|
39
|
+
));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
headers.append(pair.entry(0)?, pair.entry(1)?)?;
|
|
43
|
+
}
|
|
44
|
+
Ok(headers)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/// Convert a Ruby String or Symbol into a normalized HTTP header name.
|
|
48
|
+
///
|
|
49
|
+
/// Symbol underscores are changed to hyphens before [`HeaderName`] validates
|
|
50
|
+
/// and normalizes the name. Other Ruby types produce `Wreq::BuilderError`.
|
|
51
|
+
pub(super) fn parse_header_name(value: Value) -> Result<HeaderName, Error> {
|
|
52
|
+
let name = match (RString::from_value(value), Symbol::from_value(value)) {
|
|
53
|
+
(Some(name), _) => name.to_bytes(),
|
|
54
|
+
(None, Some(name)) => Bytes::from(name.name()?.replace('_', "-")),
|
|
55
|
+
(None, None) => {
|
|
56
|
+
return Err(type_value_error_to_magnus(
|
|
57
|
+
"Expected a String or Symbol header name",
|
|
58
|
+
));
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
HeaderName::from_bytes(name.as_ref()).map_err(header_name_error_to_magnus)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/// Convert a Ruby String or Array of Strings into validated header values.
|
|
65
|
+
///
|
|
66
|
+
/// Each Array element becomes one [`HeaderValue`]. An empty Array therefore
|
|
67
|
+
/// produces no values, allowing `set` to remove a header and `append` to do
|
|
68
|
+
/// nothing.
|
|
69
|
+
pub(super) fn parse_header_values(value: Value) -> Result<Vec<HeaderValue>, Error> {
|
|
70
|
+
if let Some(values) = RArray::from_value(value) {
|
|
71
|
+
values.into_iter().map(parse_header_value).collect()
|
|
72
|
+
} else {
|
|
73
|
+
Ok(vec![parse_header_value(value)?])
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/// Convert one Ruby String into a validated HTTP header value.
|
|
78
|
+
///
|
|
79
|
+
/// Invalid Ruby types and bytes rejected by [`HeaderValue`] are mapped to
|
|
80
|
+
/// `Wreq::BuilderError`.
|
|
81
|
+
fn parse_header_value(value: Value) -> Result<HeaderValue, Error> {
|
|
82
|
+
let value = RString::try_convert(value)
|
|
83
|
+
.map_err(|_| type_value_error_to_magnus("Expected a String header value"))?;
|
|
84
|
+
HeaderValue::from_maybe_shared(value.to_bytes()).map_err(header_value_error_to_magnus)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/// Validate the resulting number of header occurrences before a mutation.
|
|
88
|
+
///
|
|
89
|
+
/// `current` is the collection length, `replaced` is the number of existing
|
|
90
|
+
/// occurrences removed by `set`, and `added` is the incoming value count.
|
|
91
|
+
/// Checked arithmetic prevents overflow; an invalid calculation or a result
|
|
92
|
+
/// above the native [`HeaderMap`](http::HeaderMap) limit returns
|
|
93
|
+
/// `Wreq::BuilderError` without mutating the collection.
|
|
94
|
+
pub(super) fn ensure_header_count(
|
|
95
|
+
current: usize,
|
|
96
|
+
replaced: usize,
|
|
97
|
+
added: usize,
|
|
98
|
+
) -> Result<(), Error> {
|
|
99
|
+
let count = current
|
|
100
|
+
.checked_sub(replaced)
|
|
101
|
+
.and_then(|count| count.checked_add(added));
|
|
102
|
+
if count.is_some_and(|count| count <= MAX_HEADER_ENTRIES) {
|
|
103
|
+
Ok(())
|
|
104
|
+
} else {
|
|
105
|
+
Err(header_count_error())
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/// Build the error returned when the native header map reaches its entry limit.
|
|
110
|
+
pub(super) fn header_count_error() -> Error {
|
|
111
|
+
type_value_error_to_magnus("Header collection exceeds 32,768 entries")
|
|
112
|
+
}
|