patron 0.13.3 → 0.13.4
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/.github/workflows/ci.yml +26 -0
- data/.travis.yml +10 -7
- data/CHANGELOG.md +12 -1
- data/Gemfile +14 -1
- data/README.md +51 -30
- data/ext/patron/extconf.rb +3 -4
- data/ext/patron/session_ext.c +93 -98
- data/lib/patron/request.rb +2 -2
- data/lib/patron/session.rb +2 -4
- data/lib/patron/version.rb +1 -1
- data/patron.gemspec +6 -14
- data/spec/session_spec.rb +113 -60
- data/spec/session_ssl_spec.rb +39 -21
- data/spec/spec_helper.rb +10 -7
- data/spec/support/config.ru +11 -3
- data/spec/support/fork.rb +70 -0
- metadata +11 -123
data/spec/session_ssl_spec.rb
CHANGED
@@ -6,6 +6,14 @@ require 'fileutils'
|
|
6
6
|
|
7
7
|
describe Patron::Session do
|
8
8
|
|
9
|
+
def yaml_load(str)
|
10
|
+
if RUBY_VERSION >= '3.1.0'
|
11
|
+
YAML::safe_load(str, permitted_classes: [OpenStruct])
|
12
|
+
else
|
13
|
+
YAML::load(str)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
9
17
|
before(:each) do
|
10
18
|
@session = Patron::Session.new
|
11
19
|
@session.base_url = "https://localhost:9043"
|
@@ -14,7 +22,7 @@ describe Patron::Session do
|
|
14
22
|
|
15
23
|
it "should retrieve a url with :get" do
|
16
24
|
response = @session.get("/test")
|
17
|
-
body =
|
25
|
+
body = yaml_load(response.body)
|
18
26
|
expect(body.request_method).to be == "GET"
|
19
27
|
end
|
20
28
|
|
@@ -22,7 +30,7 @@ describe Patron::Session do
|
|
22
30
|
tmpfile = "/tmp/patron_test.yaml"
|
23
31
|
response = @session.get_file "/test", tmpfile
|
24
32
|
expect(response.body).to be_nil
|
25
|
-
body =
|
33
|
+
body = yaml_load(File.open(tmpfile).read)
|
26
34
|
expect(body.request_method).to be == "GET"
|
27
35
|
FileUtils.rm tmpfile
|
28
36
|
end
|
@@ -39,7 +47,7 @@ describe Patron::Session do
|
|
39
47
|
pid = fork do
|
40
48
|
response = @session.get_file "/test", tmpfile
|
41
49
|
expect(response.body).to be_nil
|
42
|
-
body =
|
50
|
+
body = yaml_load(File.open(tmpfile).read)
|
43
51
|
expect(body.request_method).to be == "GET"
|
44
52
|
FileUtils.rm tmpfile
|
45
53
|
end
|
@@ -58,14 +66,14 @@ describe Patron::Session do
|
|
58
66
|
|
59
67
|
it "should include custom headers in a request" do
|
60
68
|
response = @session.get("/test", {"User-Agent" => "PatronTest"})
|
61
|
-
body =
|
69
|
+
body = yaml_load(response.body)
|
62
70
|
expect(body.header["user-agent"]).to be == ["PatronTest"]
|
63
71
|
end
|
64
72
|
|
65
73
|
it "should merge custom headers with session headers" do
|
66
74
|
@session.headers["X-Test"] = "Testing"
|
67
75
|
response = @session.get("/test", {"User-Agent" => "PatronTest"})
|
68
|
-
body =
|
76
|
+
body = yaml_load(response.body)
|
69
77
|
expect(body.header["user-agent"]).to be == ["PatronTest"]
|
70
78
|
expect(body.header["x-test"]).to be == ["Testing"]
|
71
79
|
end
|
@@ -80,7 +88,7 @@ describe Patron::Session do
|
|
80
88
|
it "should follow redirects by default" do
|
81
89
|
@session.max_redirects = 1
|
82
90
|
response = @session.get("/redirect")
|
83
|
-
body =
|
91
|
+
body = yaml_load(response.body)
|
84
92
|
expect(response.status).to be == 200
|
85
93
|
expect(body.path).to be == "/test"
|
86
94
|
end
|
@@ -107,26 +115,26 @@ describe Patron::Session do
|
|
107
115
|
|
108
116
|
it "should send a delete request with :delete" do
|
109
117
|
response = @session.delete("/test")
|
110
|
-
body =
|
118
|
+
body = yaml_load(response.body)
|
111
119
|
expect(body.request_method).to be == "DELETE"
|
112
120
|
end
|
113
121
|
|
114
122
|
it "should send a COPY request with :copy" do
|
115
123
|
response = @session.copy("/test", "/test2")
|
116
|
-
body =
|
124
|
+
body = yaml_load(response.body)
|
117
125
|
expect(body.request_method).to be == "COPY"
|
118
126
|
end
|
119
127
|
|
120
128
|
it "should include a Destination header in COPY requests" do
|
121
129
|
response = @session.copy("/test", "/test2")
|
122
|
-
body =
|
130
|
+
body = yaml_load(response.body)
|
123
131
|
expect(body.header['destination'].first).to be == "/test2"
|
124
132
|
end
|
125
133
|
|
126
134
|
it "should upload data with :get" do
|
127
135
|
data = "upload data"
|
128
136
|
response = @session.request(:get, "/test", {}, :data => data)
|
129
|
-
body =
|
137
|
+
body = yaml_load(response.body)
|
130
138
|
expect(body.request_method).to be == "GET"
|
131
139
|
expect(body.header['content-length']).to be == [data.size.to_s]
|
132
140
|
end
|
@@ -134,7 +142,7 @@ describe Patron::Session do
|
|
134
142
|
it "should upload data with :put" do
|
135
143
|
data = "upload data"
|
136
144
|
response = @session.put("/test", data)
|
137
|
-
body =
|
145
|
+
body = yaml_load(response.body)
|
138
146
|
expect(body.request_method).to be == "PUT"
|
139
147
|
expect(body.header['content-length']).to be == [data.size.to_s]
|
140
148
|
end
|
@@ -145,7 +153,7 @@ describe Patron::Session do
|
|
145
153
|
|
146
154
|
it "should upload a file with :put" do
|
147
155
|
response = @session.put_file("/test", "LICENSE")
|
148
|
-
body =
|
156
|
+
body = yaml_load(response.body)
|
149
157
|
expect(body.request_method).to be == "PUT"
|
150
158
|
end
|
151
159
|
|
@@ -155,14 +163,14 @@ describe Patron::Session do
|
|
155
163
|
|
156
164
|
it "should use chunked encoding when uploading a file with :put" do
|
157
165
|
response = @session.put_file("/test", "LICENSE")
|
158
|
-
body =
|
166
|
+
body = yaml_load(response.body)
|
159
167
|
expect(body.header['transfer-encoding'].first).to be == "chunked"
|
160
168
|
end
|
161
169
|
|
162
170
|
it "should upload data with :post" do
|
163
171
|
data = "upload data"
|
164
172
|
response = @session.post("/test", data)
|
165
|
-
body =
|
173
|
+
body = yaml_load(response.body)
|
166
174
|
expect(body.request_method).to be == "POST"
|
167
175
|
expect(body.header['content-length']).to be == [data.size.to_s]
|
168
176
|
end
|
@@ -170,7 +178,7 @@ describe Patron::Session do
|
|
170
178
|
it "should post a hash of arguments as a urlencoded form" do
|
171
179
|
data = {:foo => 123, 'baz' => '++hello world++'}
|
172
180
|
response = @session.post("/testpost", data)
|
173
|
-
body =
|
181
|
+
body = yaml_load(response.body)
|
174
182
|
expect(body['content_type']).to be == "application/x-www-form-urlencoded"
|
175
183
|
expect(body['body']).to match(/baz=%2B%2Bhello%20world%2B%2B/)
|
176
184
|
expect(body['body']).to match(/foo=123/)
|
@@ -182,13 +190,13 @@ describe Patron::Session do
|
|
182
190
|
|
183
191
|
it "should upload a file with :post" do
|
184
192
|
response = @session.post_file("/test", "LICENSE")
|
185
|
-
body =
|
193
|
+
body = yaml_load(response.body)
|
186
194
|
expect(body.request_method).to be == "POST"
|
187
195
|
end
|
188
196
|
|
189
197
|
it "should upload a multipart with :post" do
|
190
198
|
response = @session.post_multipart("/test", { :test_data => "123" }, { :test_file => "LICENSE" } )
|
191
|
-
body =
|
199
|
+
body = yaml_load(response.body)
|
192
200
|
expect(body.request_method).to be == "POST"
|
193
201
|
end
|
194
202
|
|
@@ -198,19 +206,19 @@ describe Patron::Session do
|
|
198
206
|
|
199
207
|
it "should use chunked encoding when uploading a file with :post" do
|
200
208
|
response = @session.post_file("/test", "LICENSE")
|
201
|
-
body =
|
209
|
+
body = yaml_load(response.body)
|
202
210
|
expect(body.header['transfer-encoding'].first).to be == "chunked"
|
203
211
|
end
|
204
212
|
|
205
213
|
it "should handle cookies if set" do
|
206
214
|
@session.handle_cookies
|
207
215
|
response = @session.get("/setcookie").body
|
208
|
-
expect(
|
216
|
+
expect(yaml_load(response).header['cookie'].first).to be == "session_id=foo123"
|
209
217
|
end
|
210
218
|
|
211
219
|
it "should not handle cookies by default" do
|
212
220
|
response = @session.get("/setcookie").body
|
213
|
-
expect(
|
221
|
+
expect(yaml_load(response).header).to_not include('cookie')
|
214
222
|
end
|
215
223
|
|
216
224
|
it "should raise exception if cookie store is not writable or readable" do
|
@@ -241,10 +249,20 @@ describe Patron::Session do
|
|
241
249
|
@session.insecure = nil
|
242
250
|
@session.cacert = File.join(__dir__, 'support', 'certs', 'cacert.pem')
|
243
251
|
response = @session.get("/test")
|
244
|
-
body =
|
252
|
+
body = yaml_load(response.body)
|
245
253
|
expect(body.request_method).to be == "GET"
|
246
254
|
end
|
247
255
|
|
256
|
+
it "should allow to specify insecure mode per-request" do
|
257
|
+
@session.insecure = false
|
258
|
+
expect {
|
259
|
+
@session.request(:get, "/test", {}, insecure: true)
|
260
|
+
}.not_to raise_error
|
261
|
+
expect {
|
262
|
+
@session.request(:get, "/test", {})
|
263
|
+
}.to raise_error(Patron::Error)
|
264
|
+
end
|
265
|
+
|
248
266
|
it "should work with different SSL versions" do
|
249
267
|
['TLSv1_0','TLSv1_1'].each do |version|
|
250
268
|
@session.ssl_version = version
|
data/spec/spec_helper.rb
CHANGED
@@ -18,14 +18,17 @@ $stderr.puts "Build against #{Patron.libcurl_version}"
|
|
18
18
|
|
19
19
|
Dir['./spec/support/**/*.rb'].each { |fn| require fn }
|
20
20
|
|
21
|
-
|
22
|
-
|
21
|
+
http_server = Fork.new { PatronTestServer.start(false, 9001) }
|
22
|
+
|
23
|
+
sleep 0.1 # Don't interfere the start up output of two processes.
|
24
|
+
|
25
|
+
https_server = Fork.new { PatronTestServer.start(true, 9043) }
|
23
26
|
|
24
27
|
RSpec.configure do |c|
|
25
28
|
c.after(:suite) do
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
29
|
+
http_server.kill("TERM")
|
30
|
+
https_server.kill("TERM")
|
31
|
+
http_server.wait
|
32
|
+
https_server.wait
|
30
33
|
end
|
31
|
-
end
|
34
|
+
end
|
data/spec/support/config.ru
CHANGED
@@ -67,7 +67,7 @@ TimeoutServlet = Proc.new {|env|
|
|
67
67
|
SlowServlet = Proc.new {|env|
|
68
68
|
body = Enumerator.new do |y|
|
69
69
|
y.yield 'x'
|
70
|
-
sleep
|
70
|
+
sleep 5
|
71
71
|
y.yield 'rest of body'
|
72
72
|
end
|
73
73
|
[200, {'Content-Type' => 'text/plain'}, body]
|
@@ -102,7 +102,13 @@ RepetitiveHeaderServlet = Proc.new {|env|
|
|
102
102
|
}
|
103
103
|
|
104
104
|
PictureServlet = Proc.new {|env|
|
105
|
-
|
105
|
+
# Rack::File allows us to test Range support as well
|
106
|
+
env_with_adjusted_path = env.merge('PATH_INFO' => Rack::Utils.escape('/pic.png'))
|
107
|
+
Rack::File.new('./').call(env_with_adjusted_path)
|
108
|
+
}
|
109
|
+
|
110
|
+
RedirectToPictureServlet = Proc.new {|env|
|
111
|
+
[307, {'Location' => '/picture'}, []]
|
106
112
|
}
|
107
113
|
|
108
114
|
WrongContentLengthServlet = Proc.new {|env|
|
@@ -111,10 +117,11 @@ WrongContentLengthServlet = Proc.new {|env|
|
|
111
117
|
|
112
118
|
# Serves a substantial amount of data
|
113
119
|
LargeServlet = Proc.new {|env|
|
120
|
+
rng = Random.new
|
114
121
|
len = 15 * 1024 * 1024
|
115
122
|
body = Enumerator.new do |y|
|
116
123
|
15.times do
|
117
|
-
y.yield(
|
124
|
+
y.yield(rng.bytes(1024 * 1024))
|
118
125
|
end
|
119
126
|
end
|
120
127
|
[200, {'Content-Type' => 'binary/octet-stream', 'Content-Length' => len.to_s}, body]
|
@@ -130,6 +137,7 @@ run Rack::URLMap.new({
|
|
130
137
|
"/redirect" => RedirectServlet,
|
131
138
|
"/evil-redirect" => EvilRedirectServlet,
|
132
139
|
"/picture" => PictureServlet,
|
140
|
+
"/redirect-to-picture" => RedirectToPictureServlet,
|
133
141
|
"/very-large" => LargeServlet,
|
134
142
|
"/setcookie" => SetCookieServlet,
|
135
143
|
"/repetitiveheader" => RepetitiveHeaderServlet,
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
class Fork
|
4
|
+
def initialize(&block)
|
5
|
+
@mu = Mutex.new
|
6
|
+
@cond = ConditionVariable.new
|
7
|
+
@pstate = nil
|
8
|
+
@pid = Process.fork(&block)
|
9
|
+
@killed = false
|
10
|
+
|
11
|
+
# Start monitoring the PID.
|
12
|
+
Thread.new { monitor }
|
13
|
+
|
14
|
+
# Kill the process anyway when the program exits.
|
15
|
+
ppid = Process.pid
|
16
|
+
at_exit do
|
17
|
+
if ppid == Process.pid # Make sure we are not inside another fork spawned by rspec example.
|
18
|
+
do_kill("KILL")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Wait for process to exit.
|
24
|
+
def wait(timeout = nil)
|
25
|
+
@mu.synchronize do
|
26
|
+
next @pstate unless @pstate.nil?
|
27
|
+
|
28
|
+
@cond.wait(@mu, timeout)
|
29
|
+
@pstate
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Signal the process.
|
34
|
+
def kill(sig)
|
35
|
+
already_killed = @mu.synchronize do
|
36
|
+
old = @killed
|
37
|
+
@killed = true
|
38
|
+
old
|
39
|
+
end
|
40
|
+
signaled = do_kill(sig)
|
41
|
+
Thread.new { reaper } if signaled && !already_killed
|
42
|
+
signaled
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
# Signal the process.
|
48
|
+
def do_kill(sig)
|
49
|
+
Process.kill(sig, @pid)
|
50
|
+
true
|
51
|
+
rescue Errno::ESRCH # No such process
|
52
|
+
false
|
53
|
+
end
|
54
|
+
|
55
|
+
# Monitor the process state.
|
56
|
+
def monitor
|
57
|
+
_, pstate = Process.wait2(@pid)
|
58
|
+
|
59
|
+
@mu.synchronize do
|
60
|
+
@pstate = pstate
|
61
|
+
@cond.broadcast
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Wait 500 milliseconds and force terminate.
|
66
|
+
def reaper
|
67
|
+
pstate = wait(0.5)
|
68
|
+
do_kill("KILL") unless pstate
|
69
|
+
end
|
70
|
+
end
|
metadata
CHANGED
@@ -1,136 +1,24 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: patron
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.13.
|
4
|
+
version: 0.13.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
|
-
-
|
8
|
-
autorequire:
|
7
|
+
- Aeryn Riley Dowling-Toland
|
9
8
|
bindir: exe
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
12
|
-
dependencies:
|
13
|
-
- !ruby/object:Gem::Dependency
|
14
|
-
name: rake
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
16
|
-
requirements:
|
17
|
-
- - "~>"
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: '10'
|
20
|
-
type: :development
|
21
|
-
prerelease: false
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
23
|
-
requirements:
|
24
|
-
- - "~>"
|
25
|
-
- !ruby/object:Gem::Version
|
26
|
-
version: '10'
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: bundler
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - ">="
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: '0'
|
34
|
-
type: :development
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - ">="
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: '0'
|
41
|
-
- !ruby/object:Gem::Dependency
|
42
|
-
name: rspec
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
44
|
-
requirements:
|
45
|
-
- - ">="
|
46
|
-
- !ruby/object:Gem::Version
|
47
|
-
version: 2.3.0
|
48
|
-
type: :development
|
49
|
-
prerelease: false
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
51
|
-
requirements:
|
52
|
-
- - ">="
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
version: 2.3.0
|
55
|
-
- !ruby/object:Gem::Dependency
|
56
|
-
name: simplecov
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
58
|
-
requirements:
|
59
|
-
- - "~>"
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: '0.10'
|
62
|
-
type: :development
|
63
|
-
prerelease: false
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
65
|
-
requirements:
|
66
|
-
- - "~>"
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
version: '0.10'
|
69
|
-
- !ruby/object:Gem::Dependency
|
70
|
-
name: yard
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
72
|
-
requirements:
|
73
|
-
- - "~>"
|
74
|
-
- !ruby/object:Gem::Version
|
75
|
-
version: 0.9.11
|
76
|
-
type: :development
|
77
|
-
prerelease: false
|
78
|
-
version_requirements: !ruby/object:Gem::Requirement
|
79
|
-
requirements:
|
80
|
-
- - "~>"
|
81
|
-
- !ruby/object:Gem::Version
|
82
|
-
version: 0.9.11
|
83
|
-
- !ruby/object:Gem::Dependency
|
84
|
-
name: rack
|
85
|
-
requirement: !ruby/object:Gem::Requirement
|
86
|
-
requirements:
|
87
|
-
- - "~>"
|
88
|
-
- !ruby/object:Gem::Version
|
89
|
-
version: '1'
|
90
|
-
type: :development
|
91
|
-
prerelease: false
|
92
|
-
version_requirements: !ruby/object:Gem::Requirement
|
93
|
-
requirements:
|
94
|
-
- - "~>"
|
95
|
-
- !ruby/object:Gem::Version
|
96
|
-
version: '1'
|
97
|
-
- !ruby/object:Gem::Dependency
|
98
|
-
name: puma
|
99
|
-
requirement: !ruby/object:Gem::Requirement
|
100
|
-
requirements:
|
101
|
-
- - "~>"
|
102
|
-
- !ruby/object:Gem::Version
|
103
|
-
version: '3.11'
|
104
|
-
type: :development
|
105
|
-
prerelease: false
|
106
|
-
version_requirements: !ruby/object:Gem::Requirement
|
107
|
-
requirements:
|
108
|
-
- - "~>"
|
109
|
-
- !ruby/object:Gem::Version
|
110
|
-
version: '3.11'
|
111
|
-
- !ruby/object:Gem::Dependency
|
112
|
-
name: rake-compiler
|
113
|
-
requirement: !ruby/object:Gem::Requirement
|
114
|
-
requirements:
|
115
|
-
- - ">="
|
116
|
-
- !ruby/object:Gem::Version
|
117
|
-
version: '0'
|
118
|
-
type: :development
|
119
|
-
prerelease: false
|
120
|
-
version_requirements: !ruby/object:Gem::Requirement
|
121
|
-
requirements:
|
122
|
-
- - ">="
|
123
|
-
- !ruby/object:Gem::Version
|
124
|
-
version: '0'
|
10
|
+
date: 2025-02-27 00:00:00.000000000 Z
|
11
|
+
dependencies: []
|
125
12
|
description: Ruby HTTP client library based on libcurl
|
126
13
|
email:
|
127
|
-
-
|
14
|
+
- aeryn.toland@gmail.com
|
128
15
|
executables: []
|
129
16
|
extensions:
|
130
17
|
- ext/patron/extconf.rb
|
131
18
|
extra_rdoc_files: []
|
132
19
|
files:
|
133
20
|
- ".autotest"
|
21
|
+
- ".github/workflows/ci.yml"
|
134
22
|
- ".gitignore"
|
135
23
|
- ".rspec"
|
136
24
|
- ".travis.yml"
|
@@ -176,16 +64,18 @@ files:
|
|
176
64
|
- spec/support/certs/cacert.pem
|
177
65
|
- spec/support/certs/privkey.pem
|
178
66
|
- spec/support/config.ru
|
67
|
+
- spec/support/fork.rb
|
179
68
|
- spec/support/test_server.rb
|
180
69
|
- spec/util_spec.rb
|
181
70
|
homepage: https://github.com/toland/patron
|
182
|
-
licenses:
|
71
|
+
licenses:
|
72
|
+
- MIT
|
183
73
|
metadata:
|
184
74
|
allowed_push_host: https://rubygems.org
|
185
75
|
post_install_message: |2
|
186
76
|
|
187
77
|
Thank you for installing Patron. On OSX, make sure you are using libCURL with OpenSSL.
|
188
|
-
SecureTransport-based builds might cause crashes in forking
|
78
|
+
SecureTransport-based builds might cause crashes in forking environments.
|
189
79
|
|
190
80
|
For more info see https://github.com/curl/curl/issues/788
|
191
81
|
rdoc_options: []
|
@@ -203,9 +93,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
203
93
|
- !ruby/object:Gem::Version
|
204
94
|
version: 1.2.0
|
205
95
|
requirements: []
|
206
|
-
|
207
|
-
rubygems_version: 2.7.6
|
208
|
-
signing_key:
|
96
|
+
rubygems_version: 3.6.2
|
209
97
|
specification_version: 4
|
210
98
|
summary: Patron HTTP Client
|
211
99
|
test_files: []
|