wreq 1.2.3-x64-mingw-ucrt
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 +7 -0
- data/Cargo.lock +1687 -0
- data/Cargo.toml +54 -0
- data/Gemfile +18 -0
- data/LICENSE +201 -0
- data/README.md +153 -0
- data/Rakefile +90 -0
- data/build.rs +16 -0
- data/docs/windows-gnu-tokio-crash.md +70 -0
- data/examples/body.rb +42 -0
- data/examples/client.rb +33 -0
- data/examples/cookie.rb +24 -0
- data/examples/emulate_request.rb +37 -0
- data/examples/headers.rb +27 -0
- data/examples/proxy.rb +113 -0
- data/examples/send_stream.rb +85 -0
- data/examples/stream.rb +14 -0
- data/examples/thread_interrupt.rb +83 -0
- data/extconf.rb +7 -0
- data/lib/wreq.rb +303 -0
- data/lib/wreq_ruby/3.3/wreq_ruby.so +0 -0
- data/lib/wreq_ruby/3.4/wreq_ruby.so +0 -0
- data/lib/wreq_ruby/4.0/wreq_ruby.so +0 -0
- data/lib/wreq_ruby/body.rb +36 -0
- data/lib/wreq_ruby/client.rb +526 -0
- data/lib/wreq_ruby/cookie.rb +156 -0
- data/lib/wreq_ruby/emulate.rb +232 -0
- data/lib/wreq_ruby/error.rb +157 -0
- data/lib/wreq_ruby/header.rb +205 -0
- data/lib/wreq_ruby/http.rb +160 -0
- data/lib/wreq_ruby/response.rb +209 -0
- data/script/build_platform_gem.rb +34 -0
- data/script/build_windows_gnu.ps1 +257 -0
- data/src/arch.rs +33 -0
- data/src/client/body/form.rs +2 -0
- data/src/client/body/json.rs +16 -0
- data/src/client/body/stream.rs +146 -0
- data/src/client/body.rs +57 -0
- data/src/client/param.rs +19 -0
- data/src/client/query.rs +2 -0
- data/src/client/req.rs +274 -0
- data/src/client/resp.rs +248 -0
- data/src/client.rs +413 -0
- data/src/cookie.rs +312 -0
- data/src/emulate.rs +376 -0
- data/src/error.rs +163 -0
- data/src/extractor.rs +117 -0
- data/src/gvl.rs +154 -0
- data/src/header.rs +245 -0
- data/src/http.rs +142 -0
- data/src/lib.rs +98 -0
- data/src/macros.rs +123 -0
- data/src/rt.rs +46 -0
- data/test/client_cookie_test.rb +46 -0
- data/test/client_test.rb +136 -0
- data/test/cookie_test.rb +182 -0
- data/test/emulation_test.rb +21 -0
- data/test/error_handling_test.rb +92 -0
- data/test/header_test.rb +290 -0
- data/test/inspect_test.rb +125 -0
- data/test/module_methods_test.rb +75 -0
- data/test/orig_header_test.rb +115 -0
- data/test/request_parameters_test.rb +175 -0
- data/test/request_test.rb +244 -0
- data/test/response_test.rb +69 -0
- data/test/stream_test.rb +397 -0
- data/test/test_helper.rb +52 -0
- data/wreq.gemspec +68 -0
- metadata +123 -0
data/examples/cookie.rb
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require_relative "../lib/wreq"
|
|
4
|
+
|
|
5
|
+
# Make a client
|
|
6
|
+
client = Wreq::Client.new
|
|
7
|
+
|
|
8
|
+
# Send a GET request with cookies provided as a Hash.
|
|
9
|
+
# This form is serialized as multiple Cookie header fields (common in HTTP/2).
|
|
10
|
+
resp = client.get(
|
|
11
|
+
"https://tls.browserleaks.com",
|
|
12
|
+
cookies: {"foo" => "bar", "baz" => "qux"}
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
puts resp.text
|
|
16
|
+
|
|
17
|
+
# Send a GET request with cookies provided as a single Cookie header string.
|
|
18
|
+
# This form is common in HTTP/1.1: one Cookie header with '; ' separated pairs.
|
|
19
|
+
resp = client.get(
|
|
20
|
+
"https://tls.browserleaks.com",
|
|
21
|
+
cookies: "foo=bar; baz=qux"
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
puts resp.text
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require_relative "../lib/wreq"
|
|
4
|
+
|
|
5
|
+
# Example: How to use Emulation options in Wreq requests
|
|
6
|
+
#
|
|
7
|
+
# Emulation can be set globally on the client (applies to all requests),
|
|
8
|
+
# or per-request (overrides client default for that call).
|
|
9
|
+
#
|
|
10
|
+
# Global client emulation:
|
|
11
|
+
# Set when creating the Wreq::Client instance.
|
|
12
|
+
# All requests from this client will use the specified emulation unless overridden.
|
|
13
|
+
client = Wreq::Client.new(emulation: Wreq::Emulation.new(
|
|
14
|
+
profile: Wreq::Profile::Chrome142,
|
|
15
|
+
platform: Wreq::Platform::MacOS,
|
|
16
|
+
http2: true,
|
|
17
|
+
headers: true
|
|
18
|
+
))
|
|
19
|
+
|
|
20
|
+
resp = client.get("https://tls.peet.ws/api/all")
|
|
21
|
+
puts resp.text
|
|
22
|
+
|
|
23
|
+
# Per-request emulation:
|
|
24
|
+
# Pass the emulation option to a single request method.
|
|
25
|
+
# This allows you to override the client default for specific calls.
|
|
26
|
+
resp = client.get(
|
|
27
|
+
"https://tls.peet.ws/api/all",
|
|
28
|
+
emulation: Wreq::Emulation.new(
|
|
29
|
+
profile: Wreq::Profile::Safari26,
|
|
30
|
+
platform: Wreq::Platform::MacOS,
|
|
31
|
+
http2: true,
|
|
32
|
+
headers: true
|
|
33
|
+
),
|
|
34
|
+
# Skip client default headers for this request
|
|
35
|
+
default_headers: false
|
|
36
|
+
)
|
|
37
|
+
puts resp.text
|
data/examples/headers.rb
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require_relative "../lib/wreq"
|
|
4
|
+
|
|
5
|
+
# Make a request
|
|
6
|
+
client = Wreq::Client.new
|
|
7
|
+
response = client.get("https://httpbin.io/headers")
|
|
8
|
+
|
|
9
|
+
puts "\n=== Iterating Over All Headers ==="
|
|
10
|
+
response.headers.each do |name, value|
|
|
11
|
+
puts "#{name}: #{value}"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
puts "\n=== Response Summary ==="
|
|
15
|
+
puts "Status: #{response.code}"
|
|
16
|
+
puts "Version: #{response.version}"
|
|
17
|
+
puts "URI: #{response.url}"
|
|
18
|
+
puts "Content Length: #{response.content_length || "Unknown"}"
|
|
19
|
+
puts response.headers
|
|
20
|
+
|
|
21
|
+
if response.local_addr
|
|
22
|
+
puts "Local Address: #{response.local_addr}"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
if response.remote_addr
|
|
26
|
+
puts "Remote Address: #{response.remote_addr}"
|
|
27
|
+
end
|
data/examples/proxy.rb
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require_relative "../lib/wreq"
|
|
4
|
+
|
|
5
|
+
# Proxy templates:
|
|
6
|
+
# HTTP/HTTPS proxy: http://127.0.0.1:6152
|
|
7
|
+
# SOCKS5 proxy: socks5://127.0.0.1:6153
|
|
8
|
+
|
|
9
|
+
puts "=" * 60
|
|
10
|
+
puts "Wreq Ruby Proxy Examples"
|
|
11
|
+
puts "=" * 60
|
|
12
|
+
|
|
13
|
+
# ==============================================================================
|
|
14
|
+
# Example 1: Basic HTTP Proxy
|
|
15
|
+
# ==============================================================================
|
|
16
|
+
puts "\n--- Example 1: Basic HTTP Proxy ---"
|
|
17
|
+
begin
|
|
18
|
+
client = Wreq::Client.new(
|
|
19
|
+
user_agent: "WreqClient/1.0",
|
|
20
|
+
proxy: "http://127.0.0.1:6152"
|
|
21
|
+
)
|
|
22
|
+
puts "✅ Client with HTTP proxy created"
|
|
23
|
+
|
|
24
|
+
response = client.get("https://httpbin.io/ip")
|
|
25
|
+
puts "Status: #{response.code}"
|
|
26
|
+
puts "Response: #{response.text}"
|
|
27
|
+
rescue Wreq::RequestError => e
|
|
28
|
+
puts "❌ Request failed: #{e.message}"
|
|
29
|
+
rescue => e
|
|
30
|
+
puts "❌ Error: #{e.class} - #{e.message}"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# ==============================================================================
|
|
34
|
+
# Example 2: SOCKS5 Proxy
|
|
35
|
+
# ==============================================================================
|
|
36
|
+
puts "\n--- Example 2: SOCKS5 Proxy ---"
|
|
37
|
+
begin
|
|
38
|
+
client = Wreq::Client.new(
|
|
39
|
+
user_agent: "WreqClient/1.0",
|
|
40
|
+
proxy: "socks5://127.0.0.1:6153"
|
|
41
|
+
)
|
|
42
|
+
puts "✅ Client with SOCKS5 proxy created"
|
|
43
|
+
|
|
44
|
+
response = client.get("https://httpbin.io/ip")
|
|
45
|
+
puts "Status: #{response.code}"
|
|
46
|
+
puts "Response: #{response.text}"
|
|
47
|
+
rescue Wreq::RequestError => e
|
|
48
|
+
puts "❌ Request failed: #{e.message}"
|
|
49
|
+
rescue => e
|
|
50
|
+
puts "❌ Error: #{e.class} - #{e.message}"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# ==============================================================================
|
|
54
|
+
# Example 3: SOCKS5h Proxy (DNS resolution through proxy)
|
|
55
|
+
# ==============================================================================
|
|
56
|
+
puts "\n--- Example 3: SOCKS5h Proxy (Remote DNS) ---"
|
|
57
|
+
begin
|
|
58
|
+
client = Wreq::Client.new(
|
|
59
|
+
user_agent: "WreqClient/1.0",
|
|
60
|
+
proxy: "socks5h://127.0.0.1:6153"
|
|
61
|
+
)
|
|
62
|
+
puts "✅ Client with SOCKS5h proxy created (DNS resolved by proxy)"
|
|
63
|
+
|
|
64
|
+
response = client.get("https://httpbin.io/ip")
|
|
65
|
+
puts "Status: #{response.code}"
|
|
66
|
+
puts "Response: #{response.text}"
|
|
67
|
+
rescue Wreq::RequestError => e
|
|
68
|
+
puts "❌ Request failed: #{e.message}"
|
|
69
|
+
rescue => e
|
|
70
|
+
puts "❌ Error: #{e.class} - #{e.message}"
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# ==============================================================================
|
|
74
|
+
# Example 4: HTTP Proxy with Authentication
|
|
75
|
+
# ==============================================================================
|
|
76
|
+
puts "\n--- Example 4: HTTP Proxy with Authentication ---"
|
|
77
|
+
begin
|
|
78
|
+
# Format: http://username:password@host:port
|
|
79
|
+
client = Wreq::Client.new(
|
|
80
|
+
user_agent: "WreqClient/1.0",
|
|
81
|
+
proxy: "http://user:pass@127.0.0.1:6152"
|
|
82
|
+
)
|
|
83
|
+
puts "✅ Client with authenticated HTTP proxy created"
|
|
84
|
+
|
|
85
|
+
response = client.get("https://httpbin.io/ip")
|
|
86
|
+
puts "Status: #{response.code}"
|
|
87
|
+
puts "Response: #{response.text}"
|
|
88
|
+
rescue Wreq::RequestError => e
|
|
89
|
+
puts "❌ Request failed: #{e.message}"
|
|
90
|
+
rescue => e
|
|
91
|
+
puts "❌ Error: #{e.class} - #{e.message}"
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# ==============================================================================
|
|
95
|
+
# Example 5: SOCKS5 Proxy with Authentication
|
|
96
|
+
# ==============================================================================
|
|
97
|
+
puts "\n--- Example 5: SOCKS5 Proxy with Authentication ---"
|
|
98
|
+
begin
|
|
99
|
+
# Format: socks5://username:password@host:port
|
|
100
|
+
client = Wreq::Client.new(
|
|
101
|
+
user_agent: "WreqClient/1.0",
|
|
102
|
+
proxy: "socks5://user:pass@127.0.0.1:6153"
|
|
103
|
+
)
|
|
104
|
+
puts "✅ Client with authenticated SOCKS5 proxy created"
|
|
105
|
+
|
|
106
|
+
response = client.get("https://httpbin.io/ip")
|
|
107
|
+
puts "Status: #{response.code}"
|
|
108
|
+
puts "Response: #{response.text}"
|
|
109
|
+
rescue Wreq::RequestError => e
|
|
110
|
+
puts "❌ Request failed: #{e.message}"
|
|
111
|
+
rescue => e
|
|
112
|
+
puts "❌ Error: #{e.class} - #{e.message}"
|
|
113
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require_relative "../lib/wreq"
|
|
4
|
+
require "json"
|
|
5
|
+
require "tmpdir"
|
|
6
|
+
|
|
7
|
+
# Helpers
|
|
8
|
+
ENDPOINT = ENV["WREQ_ECHO_URL"] || "https://httpbin.io/post"
|
|
9
|
+
CHUNK = 64 * 1024
|
|
10
|
+
|
|
11
|
+
def example1_simple_push
|
|
12
|
+
puts "\n=== Example 1: Simple push ==="
|
|
13
|
+
client = Wreq::Client.new
|
|
14
|
+
us = Wreq::BodySender.new(8)
|
|
15
|
+
|
|
16
|
+
producer = Thread.new do
|
|
17
|
+
5.times do |i|
|
|
18
|
+
us.push("chunk-#{i}\n")
|
|
19
|
+
sleep 0.05
|
|
20
|
+
end
|
|
21
|
+
us.close
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
resp = client.post(ENDPOINT, body: us, headers: {"Content-Type" => "text/plain"})
|
|
25
|
+
body = resp.json
|
|
26
|
+
data = body["data"] || body["json"].to_json
|
|
27
|
+
puts "Echoed bytes: #{data.bytesize}"
|
|
28
|
+
producer.join
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def example2_file_stream
|
|
32
|
+
puts "\n=== Example 2: File streaming ==="
|
|
33
|
+
client = Wreq::Client.new
|
|
34
|
+
us = Wreq::BodySender.new(16)
|
|
35
|
+
|
|
36
|
+
# Prepare a temp file (~250KB)
|
|
37
|
+
path = File.join(Dir.tmpdir, "wreq_upload_sample.bin")
|
|
38
|
+
File.binwrite(path, Random.new.bytes(2_500_00))
|
|
39
|
+
|
|
40
|
+
total = 0
|
|
41
|
+
producer = Thread.new do
|
|
42
|
+
File.open(path, "rb") do |f|
|
|
43
|
+
while (chunk = f.read(CHUNK))
|
|
44
|
+
total += chunk.bytesize
|
|
45
|
+
us.push(chunk)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
us.close
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
resp = client.post(ENDPOINT, body: us, headers: {"Content-Type" => "application/octet-stream"})
|
|
52
|
+
json = resp.json
|
|
53
|
+
echoed = (json["headers"] && json["headers"]["Content-Length"]) || (json["data"] || "").bytesize
|
|
54
|
+
puts "Sent bytes ~#{total}, echoed length: #{echoed}"
|
|
55
|
+
producer.join
|
|
56
|
+
ensure
|
|
57
|
+
File.delete(path) if path && File.exist?(path)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def example3_background_producer
|
|
61
|
+
puts "\n=== Example 3: Background thread producer ==="
|
|
62
|
+
client = Wreq::Client.new
|
|
63
|
+
us = Wreq::BodySender.new(4)
|
|
64
|
+
|
|
65
|
+
# Simulate streaming generation
|
|
66
|
+
producer = Thread.new do
|
|
67
|
+
(1..20).each do |i|
|
|
68
|
+
us.push("data-#{i},")
|
|
69
|
+
sleep 0.01
|
|
70
|
+
end
|
|
71
|
+
us.close
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
resp = client.post(ENDPOINT, body: us)
|
|
75
|
+
json = resp.json
|
|
76
|
+
data = json["data"] || ""
|
|
77
|
+
puts "Received length: #{data.bytesize}"
|
|
78
|
+
producer.join
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
if __FILE__ == $0
|
|
82
|
+
example1_simple_push
|
|
83
|
+
example2_file_stream
|
|
84
|
+
example3_background_producer
|
|
85
|
+
end
|
data/examples/stream.rb
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require_relative "../lib/wreq"
|
|
4
|
+
|
|
5
|
+
# Make a request
|
|
6
|
+
client = Wreq::Client.new
|
|
7
|
+
response = client.get("https://httpbin.io/stream/20")
|
|
8
|
+
|
|
9
|
+
# Get the streaming body receiver
|
|
10
|
+
puts "\n=== Streaming Response Body ==="
|
|
11
|
+
response.chunks do |chunk|
|
|
12
|
+
puts chunk
|
|
13
|
+
sleep 0.1 # Simulate processing time
|
|
14
|
+
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
# Tests Thread.kill interrupt support for wreq HTTP operations.
|
|
4
|
+
# All blocking operations should respond to thread interruption.
|
|
5
|
+
|
|
6
|
+
require_relative "../lib/wreq"
|
|
7
|
+
|
|
8
|
+
HANGING_URL = "http://10.255.255.1:12345/" # Non-routable, hangs forever
|
|
9
|
+
SLOW_BODY_URL = "https://httpbin.io/drip?duration=5&numbytes=5" # 5s slow body
|
|
10
|
+
|
|
11
|
+
def test_interrupt(name, kill_after: 2, max_wait: 5)
|
|
12
|
+
print "#{name}: "
|
|
13
|
+
|
|
14
|
+
start = Time.now
|
|
15
|
+
thread = Thread.new { yield }
|
|
16
|
+
|
|
17
|
+
sleep kill_after
|
|
18
|
+
thread.kill
|
|
19
|
+
killed = thread.join(max_wait)
|
|
20
|
+
|
|
21
|
+
elapsed = Time.now - start
|
|
22
|
+
|
|
23
|
+
if killed && elapsed < (kill_after + max_wait)
|
|
24
|
+
puts "PASS (killed in #{elapsed.round(1)}s)"
|
|
25
|
+
:pass
|
|
26
|
+
else
|
|
27
|
+
puts "FAIL (still alive after #{elapsed.round(1)}s)"
|
|
28
|
+
begin
|
|
29
|
+
Thread.kill(thread)
|
|
30
|
+
rescue
|
|
31
|
+
nil
|
|
32
|
+
end
|
|
33
|
+
:fail
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
puts "Thread Interrupt Test"
|
|
38
|
+
puts "=" * 40
|
|
39
|
+
|
|
40
|
+
results = []
|
|
41
|
+
|
|
42
|
+
# Test 1: Connection phase (non-routable IP)
|
|
43
|
+
results << test_interrupt("Connect phase") {
|
|
44
|
+
begin
|
|
45
|
+
Wreq.get(HANGING_URL)
|
|
46
|
+
rescue
|
|
47
|
+
nil
|
|
48
|
+
end
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
# Test 2: Connection phase with timeout
|
|
52
|
+
results << test_interrupt("Connect + timeout") {
|
|
53
|
+
begin
|
|
54
|
+
Wreq.get(HANGING_URL, timeout: 60)
|
|
55
|
+
rescue
|
|
56
|
+
nil
|
|
57
|
+
end
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
# Test 3: Body reading phase (slow drip endpoint)
|
|
61
|
+
results << test_interrupt("Body reading") {
|
|
62
|
+
resp = Wreq.get(SLOW_BODY_URL)
|
|
63
|
+
begin
|
|
64
|
+
resp.text
|
|
65
|
+
rescue
|
|
66
|
+
nil
|
|
67
|
+
end
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
# Test 4: Body streaming phase
|
|
71
|
+
results << test_interrupt("Body streaming") {
|
|
72
|
+
resp = Wreq.get(SLOW_BODY_URL)
|
|
73
|
+
begin
|
|
74
|
+
resp.chunks { |chunk| chunk }
|
|
75
|
+
rescue
|
|
76
|
+
nil
|
|
77
|
+
end
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
puts "=" * 40
|
|
81
|
+
passed = results.count(:pass)
|
|
82
|
+
puts "#{passed}/#{results.size} tests passed"
|
|
83
|
+
exit(results.all?(:pass) ? 0 : 1)
|
data/extconf.rb
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
require "mkmf"
|
|
2
|
+
# We use rb_sys for makefile generation only.
|
|
3
|
+
# We can use `RB_SYS_CARGO_PROFILE` to choose Cargo profile
|
|
4
|
+
# Read more https://github.com/oxidize-rb/rb-sys/blob/main/gem/README.md
|
|
5
|
+
require "rb_sys/mkmf"
|
|
6
|
+
|
|
7
|
+
create_rust_makefile("wreq_ruby/wreq_ruby")
|