httpclient 2.1.2 → 2.1.3
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.
- data/lib/http-access2.rb +1 -2
- data/lib/httpclient.rb +631 -1832
- data/lib/httpclient/auth.rb +510 -0
- data/lib/httpclient/connection.rb +84 -0
- data/lib/httpclient/cookie.rb +82 -71
- data/lib/httpclient/http.rb +726 -484
- data/lib/httpclient/session.rb +855 -0
- data/lib/httpclient/ssl_config.rb +379 -0
- data/lib/httpclient/timeout.rb +122 -0
- data/lib/httpclient/util.rb +86 -0
- metadata +50 -37
@@ -0,0 +1,379 @@
|
|
1
|
+
# HTTPClient - HTTP client library.
|
2
|
+
# Copyright (C) 2000-2008 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
|
3
|
+
#
|
4
|
+
# This program is copyrighted free software by NAKAMURA, Hiroshi. You can
|
5
|
+
# redistribute it and/or modify it under the same terms of Ruby's license;
|
6
|
+
# either the dual license version in 2003, or any later version.
|
7
|
+
|
8
|
+
|
9
|
+
class HTTPClient
|
10
|
+
|
11
|
+
begin
|
12
|
+
require 'openssl'
|
13
|
+
SSLEnabled = true
|
14
|
+
rescue LoadError
|
15
|
+
SSLEnabled = false
|
16
|
+
end
|
17
|
+
|
18
|
+
# Represents SSL configuration for HTTPClient instance.
|
19
|
+
# The implementation depends on OpenSSL.
|
20
|
+
#
|
21
|
+
# == Trust Anchor Control
|
22
|
+
#
|
23
|
+
# SSLConfig loads 'httpclient/cacert.p7s' as a trust anchor
|
24
|
+
# (trusted certificate(s)) with set_trust_ca in initialization time.
|
25
|
+
# This means that HTTPClient instance trusts some CA certificates by default,
|
26
|
+
# like Web browsers. 'httpclient/cacert.p7s' is created by the author and
|
27
|
+
# included in released package.
|
28
|
+
#
|
29
|
+
# 'cacert.p7s' is automatically generated from JDK 1.6.
|
30
|
+
#
|
31
|
+
# You may want to change trust anchor by yourself. Call clear_cert_store
|
32
|
+
# then set_trust_ca for that purpose.
|
33
|
+
class SSLConfig
|
34
|
+
include OpenSSL if SSLEnabled
|
35
|
+
|
36
|
+
# OpenSSL::X509::Certificate:: certificate for SSL client authenticateion.
|
37
|
+
# nil by default. (no client authenticateion)
|
38
|
+
attr_reader :client_cert
|
39
|
+
# OpenSSL::PKey::PKey:: private key for SSL client authentication.
|
40
|
+
# nil by default. (no client authenticateion)
|
41
|
+
attr_reader :client_key
|
42
|
+
|
43
|
+
# A number which represents OpenSSL's verify mode. Default value is
|
44
|
+
# OpenSSL::SSL::VERIFY_PEER | OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT.
|
45
|
+
attr_reader :verify_mode
|
46
|
+
# A number of verify depth. Certification path which length is longer than
|
47
|
+
# this depth is not allowed.
|
48
|
+
attr_reader :verify_depth
|
49
|
+
# A callback handler for custom certificate verification. nil by default.
|
50
|
+
# If the handler is set, handler.call is invoked just after general
|
51
|
+
# OpenSSL's verification. handler.call is invoked with 2 arguments,
|
52
|
+
# ok and ctx; ok is a result of general OpenSSL's verification. ctx is a
|
53
|
+
# OpenSSL::X509::StoreContext.
|
54
|
+
attr_reader :verify_callback
|
55
|
+
# SSL timeout in sec. nil by default.
|
56
|
+
attr_reader :timeout
|
57
|
+
# A number of OpenSSL's SSL options. Default value is
|
58
|
+
# OpenSSL::SSL::OP_ALL | OpenSSL::SSL::OP_NO_SSLv2
|
59
|
+
attr_reader :options
|
60
|
+
# A String of OpenSSL's cipher configuration. Default value is
|
61
|
+
# ALL:!ADH:!LOW:!EXP:!MD5:+SSLv2:@STRENGTH
|
62
|
+
# See ciphers(1) man in OpenSSL for more detail.
|
63
|
+
attr_reader :ciphers
|
64
|
+
|
65
|
+
# OpenSSL::X509::X509::Store used for verification. You can reset the
|
66
|
+
# store with clear_cert_store and set the new store with cert_store=.
|
67
|
+
attr_reader :cert_store # don't use if you don't know what it is.
|
68
|
+
|
69
|
+
# For server side configuration. Ignore this.
|
70
|
+
attr_reader :client_ca # :nodoc:
|
71
|
+
|
72
|
+
# Creates a SSLConfig.
|
73
|
+
def initialize(client)
|
74
|
+
return unless SSLEnabled
|
75
|
+
@client = client
|
76
|
+
@cert_store = X509::Store.new
|
77
|
+
@client_cert = @client_key = @client_ca = nil
|
78
|
+
@verify_mode = SSL::VERIFY_PEER | SSL::VERIFY_FAIL_IF_NO_PEER_CERT
|
79
|
+
@verify_depth = nil
|
80
|
+
@verify_callback = nil
|
81
|
+
@dest = nil
|
82
|
+
@timeout = nil
|
83
|
+
@options = defined?(SSL::OP_ALL) ? SSL::OP_ALL | SSL::OP_NO_SSLv2 : nil
|
84
|
+
@ciphers = "ALL:!ADH:!LOW:!EXP:!MD5:+SSLv2:@STRENGTH"
|
85
|
+
load_cacerts
|
86
|
+
end
|
87
|
+
|
88
|
+
# Sets certificate (OpenSSL::X509::Certificate) for SSL client
|
89
|
+
# authentication.
|
90
|
+
# client_key and client_cert must be a pair.
|
91
|
+
#
|
92
|
+
# Calling this method resets all existing sessions.
|
93
|
+
def client_cert=(client_cert)
|
94
|
+
@client_cert = client_cert
|
95
|
+
change_notify
|
96
|
+
end
|
97
|
+
|
98
|
+
# Sets private key (OpenSSL::PKey::PKey) for SSL client authentication.
|
99
|
+
# client_key and client_cert must be a pair.
|
100
|
+
#
|
101
|
+
# Calling this method resets all existing sessions.
|
102
|
+
def client_key=(client_key)
|
103
|
+
@client_key = client_key
|
104
|
+
change_notify
|
105
|
+
end
|
106
|
+
|
107
|
+
# Sets certificate and private key for SSL client authentication.
|
108
|
+
# cert_file:: must be a filename of PEM/DER formatted file.
|
109
|
+
# key_file:: must be a filename of PEM/DER formatted file. Key must be an
|
110
|
+
# RSA key. If you want to use other PKey algorithm,
|
111
|
+
# use client_key=.
|
112
|
+
#
|
113
|
+
# Calling this method resets all existing sessions.
|
114
|
+
def set_client_cert_file(cert_file, key_file)
|
115
|
+
@client_cert = X509::Certificate.new(File.open(cert_file).read)
|
116
|
+
@client_key = PKey::RSA.new(File.open(key_file).read)
|
117
|
+
change_notify
|
118
|
+
end
|
119
|
+
|
120
|
+
# Drops current certificate store (OpenSSL::X509::Store) for SSL and create
|
121
|
+
# new one for the next session.
|
122
|
+
#
|
123
|
+
# Calling this method resets all existing sessions.
|
124
|
+
def clear_cert_store
|
125
|
+
@cert_store = X509::Store.new
|
126
|
+
change_notify
|
127
|
+
end
|
128
|
+
|
129
|
+
# Sets new certificate store (OpenSSL::X509::Store).
|
130
|
+
# don't use if you don't know what it is.
|
131
|
+
#
|
132
|
+
# Calling this method resets all existing sessions.
|
133
|
+
def cert_store=(cert_store)
|
134
|
+
@cert_store = cert_store
|
135
|
+
change_notify
|
136
|
+
end
|
137
|
+
|
138
|
+
# Sets trust anchor certificate(s) for verification.
|
139
|
+
# trust_ca_file_or_hashed_dir:: a filename of a PEM/DER formatted
|
140
|
+
# OpenSSL::X509::Certificate or
|
141
|
+
# a 'c-rehash'eddirectory name which stores
|
142
|
+
# trusted certificate files.
|
143
|
+
#
|
144
|
+
# Calling this method resets all existing sessions.
|
145
|
+
def set_trust_ca(trust_ca_file_or_hashed_dir)
|
146
|
+
if FileTest.directory?(trust_ca_file_or_hashed_dir)
|
147
|
+
@cert_store.add_path(trust_ca_file_or_hashed_dir)
|
148
|
+
else
|
149
|
+
@cert_store.add_file(trust_ca_file_or_hashed_dir)
|
150
|
+
end
|
151
|
+
change_notify
|
152
|
+
end
|
153
|
+
|
154
|
+
# Adds CRL for verification.
|
155
|
+
# crl:: a OpenSSL::X509::CRL or a filename of a PEM/DER formatted
|
156
|
+
# OpenSSL::X509::CRL.
|
157
|
+
#
|
158
|
+
# Calling this method resets all existing sessions.
|
159
|
+
def set_crl(crl)
|
160
|
+
unless crl.is_a?(X509::CRL)
|
161
|
+
crl = X509::CRL.new(File.open(crl).read)
|
162
|
+
end
|
163
|
+
@cert_store.add_crl(crl)
|
164
|
+
@cert_store.flags = X509::V_FLAG_CRL_CHECK | X509::V_FLAG_CRL_CHECK_ALL
|
165
|
+
change_notify
|
166
|
+
end
|
167
|
+
|
168
|
+
# Sets verify mode of OpenSSL. New value must be a combination of
|
169
|
+
# constants OpenSSL::SSL::VERIFY_*
|
170
|
+
#
|
171
|
+
# Calling this method resets all existing sessions.
|
172
|
+
def verify_mode=(verify_mode)
|
173
|
+
@verify_mode = verify_mode
|
174
|
+
change_notify
|
175
|
+
end
|
176
|
+
|
177
|
+
# Sets verify depth. New value must be a number.
|
178
|
+
#
|
179
|
+
# Calling this method resets all existing sessions.
|
180
|
+
def verify_depth=(verify_depth)
|
181
|
+
@verify_depth = verify_depth
|
182
|
+
change_notify
|
183
|
+
end
|
184
|
+
|
185
|
+
# Sets callback handler for custom certificate verification.
|
186
|
+
# See verify_callback.
|
187
|
+
#
|
188
|
+
# Calling this method resets all existing sessions.
|
189
|
+
def verify_callback=(verify_callback)
|
190
|
+
@verify_callback = verify_callback
|
191
|
+
change_notify
|
192
|
+
end
|
193
|
+
|
194
|
+
# Sets SSL timeout in sec.
|
195
|
+
#
|
196
|
+
# Calling this method resets all existing sessions.
|
197
|
+
def timeout=(timeout)
|
198
|
+
@timeout = timeout
|
199
|
+
change_notify
|
200
|
+
end
|
201
|
+
|
202
|
+
# Sets SSL options. New value must be a combination of # constants
|
203
|
+
# OpenSSL::SSL::OP_*
|
204
|
+
#
|
205
|
+
# Calling this method resets all existing sessions.
|
206
|
+
def options=(options)
|
207
|
+
@options = options
|
208
|
+
change_notify
|
209
|
+
end
|
210
|
+
|
211
|
+
# Sets cipher configuration. New value must be a String.
|
212
|
+
#
|
213
|
+
# Calling this method resets all existing sessions.
|
214
|
+
def ciphers=(ciphers)
|
215
|
+
@ciphers = ciphers
|
216
|
+
change_notify
|
217
|
+
end
|
218
|
+
|
219
|
+
def client_ca=(client_ca) # :nodoc:
|
220
|
+
@client_ca = client_ca
|
221
|
+
change_notify
|
222
|
+
end
|
223
|
+
|
224
|
+
# interfaces for SSLSocketWrap.
|
225
|
+
def set_context(ctx) # :nodoc:
|
226
|
+
# Verification: Use Store#verify_callback instead of SSLContext#verify*?
|
227
|
+
ctx.cert_store = @cert_store
|
228
|
+
ctx.verify_mode = @verify_mode
|
229
|
+
ctx.verify_depth = @verify_depth if @verify_depth
|
230
|
+
ctx.verify_callback = @verify_callback || method(:default_verify_callback)
|
231
|
+
# SSL config
|
232
|
+
ctx.cert = @client_cert
|
233
|
+
ctx.key = @client_key
|
234
|
+
ctx.client_ca = @client_ca
|
235
|
+
ctx.timeout = @timeout
|
236
|
+
ctx.options = @options
|
237
|
+
ctx.ciphers = @ciphers
|
238
|
+
end
|
239
|
+
|
240
|
+
# post connection check proc for ruby < 1.8.5.
|
241
|
+
# this definition must match with the one in ext/openssl/lib/openssl/ssl.rb
|
242
|
+
def post_connection_check(peer_cert, hostname) # :nodoc:
|
243
|
+
check_common_name = true
|
244
|
+
cert = peer_cert
|
245
|
+
cert.extensions.each{|ext|
|
246
|
+
next if ext.oid != "subjectAltName"
|
247
|
+
ext.value.split(/,\s+/).each{|general_name|
|
248
|
+
if /\ADNS:(.*)/ =~ general_name
|
249
|
+
check_common_name = false
|
250
|
+
reg = Regexp.escape($1).gsub(/\\\*/, "[^.]+")
|
251
|
+
return true if /\A#{reg}\z/i =~ hostname
|
252
|
+
elsif /\AIP Address:(.*)/ =~ general_name
|
253
|
+
check_common_name = false
|
254
|
+
return true if $1 == hostname
|
255
|
+
end
|
256
|
+
}
|
257
|
+
}
|
258
|
+
if check_common_name
|
259
|
+
cert.subject.to_a.each{|oid, value|
|
260
|
+
if oid == "CN"
|
261
|
+
reg = Regexp.escape(value).gsub(/\\\*/, "[^.]+")
|
262
|
+
return true if /\A#{reg}\z/i =~ hostname
|
263
|
+
end
|
264
|
+
}
|
265
|
+
end
|
266
|
+
raise SSL::SSLError, "hostname was not match with the server certificate"
|
267
|
+
end
|
268
|
+
|
269
|
+
# Default callback for verification: only dumps error.
|
270
|
+
def default_verify_callback(is_ok, ctx)
|
271
|
+
if $DEBUG
|
272
|
+
puts "#{ is_ok ? 'ok' : 'ng' }: #{ctx.current_cert.subject}"
|
273
|
+
end
|
274
|
+
if !is_ok
|
275
|
+
depth = ctx.error_depth
|
276
|
+
code = ctx.error
|
277
|
+
msg = ctx.error_string
|
278
|
+
STDERR.puts "at depth #{depth} - #{code}: #{msg}"
|
279
|
+
end
|
280
|
+
is_ok
|
281
|
+
end
|
282
|
+
|
283
|
+
# Sample callback method: CAUTION: does not check CRL/ARL.
|
284
|
+
def sample_verify_callback(is_ok, ctx)
|
285
|
+
unless is_ok
|
286
|
+
depth = ctx.error_depth
|
287
|
+
code = ctx.error
|
288
|
+
msg = ctx.error_string
|
289
|
+
STDERR.puts "at depth #{depth} - #{code}: #{msg}" if $DEBUG
|
290
|
+
return false
|
291
|
+
end
|
292
|
+
|
293
|
+
cert = ctx.current_cert
|
294
|
+
self_signed = false
|
295
|
+
ca = false
|
296
|
+
pathlen = nil
|
297
|
+
server_auth = true
|
298
|
+
self_signed = (cert.subject.cmp(cert.issuer) == 0)
|
299
|
+
|
300
|
+
# Check extensions whatever its criticality is. (sample)
|
301
|
+
cert.extensions.each do |ex|
|
302
|
+
case ex.oid
|
303
|
+
when 'basicConstraints'
|
304
|
+
/CA:(TRUE|FALSE), pathlen:(\d+)/ =~ ex.value
|
305
|
+
ca = ($1 == 'TRUE')
|
306
|
+
pathlen = $2.to_i
|
307
|
+
when 'keyUsage'
|
308
|
+
usage = ex.value.split(/\s*,\s*/)
|
309
|
+
ca = usage.include?('Certificate Sign')
|
310
|
+
server_auth = usage.include?('Key Encipherment')
|
311
|
+
when 'extendedKeyUsage'
|
312
|
+
usage = ex.value.split(/\s*,\s*/)
|
313
|
+
server_auth = usage.include?('Netscape Server Gated Crypto')
|
314
|
+
when 'nsCertType'
|
315
|
+
usage = ex.value.split(/\s*,\s*/)
|
316
|
+
ca = usage.include?('SSL CA')
|
317
|
+
server_auth = usage.include?('SSL Server')
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
if self_signed
|
322
|
+
STDERR.puts 'self signing CA' if $DEBUG
|
323
|
+
return true
|
324
|
+
elsif ca
|
325
|
+
STDERR.puts 'middle level CA' if $DEBUG
|
326
|
+
return true
|
327
|
+
elsif server_auth
|
328
|
+
STDERR.puts 'for server authentication' if $DEBUG
|
329
|
+
return true
|
330
|
+
end
|
331
|
+
|
332
|
+
return false
|
333
|
+
end
|
334
|
+
|
335
|
+
private
|
336
|
+
|
337
|
+
def change_notify
|
338
|
+
@client.reset_all
|
339
|
+
end
|
340
|
+
|
341
|
+
def load_cacerts
|
342
|
+
file = File.join(File.dirname(__FILE__), 'cacert.p7s')
|
343
|
+
if File.exist?(file)
|
344
|
+
dist_cert =<<__DIST_CERT__
|
345
|
+
-----BEGIN CERTIFICATE-----
|
346
|
+
MIIC/jCCAmegAwIBAgIBATANBgkqhkiG9w0BAQUFADBNMQswCQYDVQQGEwJKUDER
|
347
|
+
MA8GA1UECgwIY3Rvci5vcmcxFDASBgNVBAsMC0RldmVsb3BtZW50MRUwEwYDVQQD
|
348
|
+
DAxodHRwLWFjY2VzczIwHhcNMDcwOTExMTM1ODMxWhcNMDkwOTEwMTM1ODMxWjBN
|
349
|
+
MQswCQYDVQQGEwJKUDERMA8GA1UECgwIY3Rvci5vcmcxFDASBgNVBAsMC0RldmVs
|
350
|
+
b3BtZW50MRUwEwYDVQQDDAxodHRwLWFjY2VzczIwgZ8wDQYJKoZIhvcNAQEBBQAD
|
351
|
+
gY0AMIGJAoGBALi66ujWtUCQm5HpMSyr/AAIFYVXC/dmn7C8TR/HMiUuW3waY4uX
|
352
|
+
LFqCDAGOX4gf177pX+b99t3mpaiAjJuqc858D9xEECzhDWgXdLbhRqWhUOble4RY
|
353
|
+
c1yWYC990IgXJDMKx7VAuZ3cBhdBxtlE9sb1ZCzmHQsvTy/OoRzcJCrTAgMBAAGj
|
354
|
+
ge0wgeowDwYDVR0TAQH/BAUwAwEB/zAxBglghkgBhvhCAQ0EJBYiUnVieS9PcGVu
|
355
|
+
U1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUJNE0GGaRKmN2qhnO
|
356
|
+
FyBWVl4Qj6owDgYDVR0PAQH/BAQDAgEGMHUGA1UdIwRuMGyAFCTRNBhmkSpjdqoZ
|
357
|
+
zhcgVlZeEI+qoVGkTzBNMQswCQYDVQQGEwJKUDERMA8GA1UECgwIY3Rvci5vcmcx
|
358
|
+
FDASBgNVBAsMC0RldmVsb3BtZW50MRUwEwYDVQQDDAxodHRwLWFjY2VzczKCAQEw
|
359
|
+
DQYJKoZIhvcNAQEFBQADgYEAH11tstSUuqFpMqoh/vM5l3Nqb8ygblbqEYQs/iG/
|
360
|
+
UeQkOZk/P1TxB6Ozn2htJ1srqDpUsncFVZ/ecP19GkeOZ6BmIhppcHhE5WyLBcPX
|
361
|
+
It5q1BW0PiAzT9LlEGoaiW0nw39so0Pr1whJDfc1t4fjdk+kSiMIzRHbTDvHWfpV
|
362
|
+
nTA=
|
363
|
+
-----END CERTIFICATE-----
|
364
|
+
__DIST_CERT__
|
365
|
+
p7 = PKCS7.read_smime(File.open(file) { |f| f.read })
|
366
|
+
selfcert = X509::Certificate.new(dist_cert)
|
367
|
+
store = X509::Store.new
|
368
|
+
store.add_cert(selfcert)
|
369
|
+
if (p7.verify(nil, store, p7.data, 0))
|
370
|
+
set_trust_ca(file)
|
371
|
+
else
|
372
|
+
STDERR.puts("cacerts: #{file} loading failed")
|
373
|
+
end
|
374
|
+
end
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
378
|
+
|
379
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
# HTTPClient - HTTP client library.
|
2
|
+
# Copyright (C) 2000-2008 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
|
3
|
+
#
|
4
|
+
# This program is copyrighted free software by NAKAMURA, Hiroshi. You can
|
5
|
+
# redistribute it and/or modify it under the same terms of Ruby's license;
|
6
|
+
# either the dual license version in 2003, or any later version.
|
7
|
+
|
8
|
+
|
9
|
+
require 'timeout'
|
10
|
+
require 'thread'
|
11
|
+
|
12
|
+
|
13
|
+
class HTTPClient
|
14
|
+
|
15
|
+
|
16
|
+
# Replaces timeout.rb to avoid Thread creation and scheduling overhead.
|
17
|
+
#
|
18
|
+
# You should check another timeout replace in WEBrick.
|
19
|
+
# See lib/webrick/utils.rb in ruby/1.9.
|
20
|
+
#
|
21
|
+
# About this implementation:
|
22
|
+
# * Do not create Thread for each timeout() call. Just create 1 Thread for
|
23
|
+
# timeout scheduler.
|
24
|
+
# * Do not wakeup the scheduler thread so often. Let scheduler thread sleep
|
25
|
+
# until the nearest period.
|
26
|
+
class TimeoutScheduler
|
27
|
+
|
28
|
+
# Represents timeout period.
|
29
|
+
class Period
|
30
|
+
attr_reader :thread, :time
|
31
|
+
|
32
|
+
# Creates new Period.
|
33
|
+
def initialize(thread, time, ex)
|
34
|
+
@thread, @time, @ex = thread, time, ex
|
35
|
+
@lock = Mutex.new
|
36
|
+
end
|
37
|
+
|
38
|
+
# Raises if thread exists and alive.
|
39
|
+
def raise(message)
|
40
|
+
@lock.synchronize do
|
41
|
+
if @thread and @thread.alive?
|
42
|
+
@thread.raise(@ex, message)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Cancel this Period. Mutex is needed to avoid too-late exception.
|
48
|
+
def cancel
|
49
|
+
@lock.synchronize do
|
50
|
+
@thread = nil
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Creates new TimeoutScheduler.
|
56
|
+
def initialize
|
57
|
+
@pool = {}
|
58
|
+
@next = nil
|
59
|
+
@thread = start_timer_thread
|
60
|
+
Thread.pass while @thread.status != 'sleep'
|
61
|
+
end
|
62
|
+
|
63
|
+
# Registers new timeout period.
|
64
|
+
def register(thread, sec, ex)
|
65
|
+
period = Period.new(thread, Time.now + sec, ex || ::Timeout::Error)
|
66
|
+
@pool[period] = true
|
67
|
+
if @next.nil? or period.time < @next
|
68
|
+
@thread.wakeup
|
69
|
+
end
|
70
|
+
period
|
71
|
+
end
|
72
|
+
|
73
|
+
# Cancels the given period.
|
74
|
+
def cancel(period)
|
75
|
+
@pool.delete(period)
|
76
|
+
period.cancel
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def start_timer_thread
|
82
|
+
Thread.new {
|
83
|
+
while true
|
84
|
+
if @pool.empty?
|
85
|
+
@next = nil
|
86
|
+
sleep
|
87
|
+
else
|
88
|
+
min, = @pool.min { |a, b| a[0].time <=> b[0].time }
|
89
|
+
@next = min.time
|
90
|
+
sec = @next - Time.now
|
91
|
+
if sec > 0
|
92
|
+
sleep(sec)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
now = Time.now
|
96
|
+
@pool.keys.each do |period|
|
97
|
+
if period.time < now
|
98
|
+
period.raise('execution expired')
|
99
|
+
cancel(period)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
}
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
TIMEOUT_SCHEDULER = TimeoutScheduler.new
|
108
|
+
|
109
|
+
module Timeout
|
110
|
+
def timeout(sec, ex = nil, &block)
|
111
|
+
return yield if sec == nil or sec.zero?
|
112
|
+
begin
|
113
|
+
period = TIMEOUT_SCHEDULER.register(Thread.current, sec, ex)
|
114
|
+
yield(sec)
|
115
|
+
ensure
|
116
|
+
TIMEOUT_SCHEDULER.cancel(period)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
|
122
|
+
end
|