maedana-httpclient 2.1.5.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,417 @@
1
+ # HTTPClient - HTTP client library.
2
+ # Copyright (C) 2000-2009 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) { |f| f.read })
116
+ @client_key = PKey::RSA.new(File.open(key_file) { |f| f.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) { |f| f.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
+ [
343
+ [DIST_CERT, 'cacert.p7s'],
344
+ [DIST_CERT_SHA1, 'cacert_sha1.p7s']
345
+ ].each do |cert_str, ca_file|
346
+ file = File.join(File.dirname(__FILE__), ca_file)
347
+ if File.exist?(file)
348
+ p7 = PKCS7.read_smime(File.open(file) { |f| f.read })
349
+ selfcert = X509::Certificate.new(cert_str)
350
+ store = X509::Store.new
351
+ store.add_cert(selfcert)
352
+ if (p7.verify(nil, store, p7.data, 0))
353
+ set_trust_ca(file)
354
+ return
355
+ end
356
+ end
357
+ end
358
+ STDERR.puts("cacerts loading failed")
359
+ end
360
+
361
+ DIST_CERT =<<__DIST_CERT__
362
+ -----BEGIN CERTIFICATE-----
363
+ MIID/TCCAuWgAwIBAgIBATANBgkqhkiG9w0BAQ0FADBLMQswCQYDVQQGEwJKUDER
364
+ MA8GA1UECgwIY3Rvci5vcmcxFDASBgNVBAsMC0RldmVsb3BtZW50MRMwEQYDVQQD
365
+ DApodHRwY2xpZW50MB4XDTA5MDUyMTEyMzkwNVoXDTM3MTIzMTIzNTk1OVowSzEL
366
+ MAkGA1UEBhMCSlAxETAPBgNVBAoMCGN0b3Iub3JnMRQwEgYDVQQLDAtEZXZlbG9w
367
+ bWVudDETMBEGA1UEAwwKaHR0cGNsaWVudDCCASIwDQYJKoZIhvcNAQEBBQADggEP
368
+ ADCCAQoCggEBAM2PlkdTH97zvIHoPIMj87wnNvpqIQUD7L/hlysO0XBsmR/XZUeU
369
+ ZKB10JQqMXviWpTnU9KU6xGTx3EI4wfd2dpLwH/d4d7K4LngW1kY7kJlZeJhakno
370
+ GzQ40RSI9WkQ0R9KOE888f7OkTBafcL8UyWFVIMhQBw2d9iNl4Jc69QojayCDoSX
371
+ XbbEP0n8yi7HwIU3RFuX6DtMpOx4/1K7Z002ccOGJ3J9kHgeDQSQtF42cQYC7qj2
372
+ 67I/OQgnB7ycxTCP0E7bdXQg+zqsngrhaoNn/+I+CoO7nD4t4uQ+B4agALh4PPxs
373
+ bQD9MCL+VurNGLYv0HVd+ZlLblpddC9PLTsCAwEAAaOB6zCB6DAPBgNVHRMBAf8E
374
+ BTADAQH/MDEGCWCGSAGG+EIBDQQkFiJSdWJ5L09wZW5TU0wgR2VuZXJhdGVkIENl
375
+ cnRpZmljYXRlMB0GA1UdDgQWBBRAnB6XlMoOcm7HVAw+JWxY205PHTAOBgNVHQ8B
376
+ Af8EBAMCAQYwcwYDVR0jBGwwaoAUQJwel5TKDnJux1QMPiVsWNtOTx2hT6RNMEsx
377
+ CzAJBgNVBAYTAkpQMREwDwYDVQQKDAhjdG9yLm9yZzEUMBIGA1UECwwLRGV2ZWxv
378
+ cG1lbnQxEzARBgNVBAMMCmh0dHBjbGllbnSCAQEwDQYJKoZIhvcNAQENBQADggEB
379
+ ABVFepybD5XqsBnOn/oDHvK0xAPMF4Ap4Ht1yMQLObg8paVhANSdqIevPlCr/mPL
380
+ DRjcy+J1fCnE6lCfsfLdTgAjirqt8pm92NccxmJ8hTmMd3LWC1n+eYWaolqTCVRM
381
+ Bpe8UY9enyXrFoudHlr9epr18E6As6VrCSfpXFZkD9WHVSWpzkB3qATu5qcDCzCH
382
+ bI0755Mdm/1hKJCD4l69h3J3OhRIEUPJfHnPvM5wtiyC2dcE9itwE/wdVzBJeIBX
383
+ JQm+Qj+K8qXcRTzZZGIBjw2n46xJgW6YncNCHU/WWfNCYwdkngHS/aN8IbEjhCwf
384
+ viXFisVrDN/+pZZGMf67ZaY=
385
+ -----END CERTIFICATE-----
386
+ __DIST_CERT__
387
+
388
+ DIST_CERT_SHA1 =<<__DIST_CERT__
389
+ -----BEGIN CERTIFICATE-----
390
+ MIID/TCCAuWgAwIBAgIBAjANBgkqhkiG9w0BAQUFADBLMQswCQYDVQQGEwJKUDER
391
+ MA8GA1UECgwIY3Rvci5vcmcxFDASBgNVBAsMC0RldmVsb3BtZW50MRMwEQYDVQQD
392
+ DApodHRwY2xpZW50MB4XDTA5MDYyNTE0MjUzN1oXDTEwMTIzMTIzNTk1OVowSzEL
393
+ MAkGA1UEBhMCSlAxETAPBgNVBAoMCGN0b3Iub3JnMRQwEgYDVQQLDAtEZXZlbG9w
394
+ bWVudDETMBEGA1UEAwwKaHR0cGNsaWVudDCCASIwDQYJKoZIhvcNAQEBBQADggEP
395
+ ADCCAQoCggEBAM2PlkdTH97zvIHoPIMj87wnNvpqIQUD7L/hlysO0XBsmR/XZUeU
396
+ ZKB10JQqMXviWpTnU9KU6xGTx3EI4wfd2dpLwH/d4d7K4LngW1kY7kJlZeJhakno
397
+ GzQ40RSI9WkQ0R9KOE888f7OkTBafcL8UyWFVIMhQBw2d9iNl4Jc69QojayCDoSX
398
+ XbbEP0n8yi7HwIU3RFuX6DtMpOx4/1K7Z002ccOGJ3J9kHgeDQSQtF42cQYC7qj2
399
+ 67I/OQgnB7ycxTCP0E7bdXQg+zqsngrhaoNn/+I+CoO7nD4t4uQ+B4agALh4PPxs
400
+ bQD9MCL+VurNGLYv0HVd+ZlLblpddC9PLTsCAwEAAaOB6zCB6DAPBgNVHRMBAf8E
401
+ BTADAQH/MDEGCWCGSAGG+EIBDQQkFiJSdWJ5L09wZW5TU0wgR2VuZXJhdGVkIENl
402
+ cnRpZmljYXRlMB0GA1UdDgQWBBRAnB6XlMoOcm7HVAw+JWxY205PHTAOBgNVHQ8B
403
+ Af8EBAMCAQYwcwYDVR0jBGwwaoAUQJwel5TKDnJux1QMPiVsWNtOTx2hT6RNMEsx
404
+ CzAJBgNVBAYTAkpQMREwDwYDVQQKDAhjdG9yLm9yZzEUMBIGA1UECwwLRGV2ZWxv
405
+ cG1lbnQxEzARBgNVBAMMCmh0dHBjbGllbnSCAQIwDQYJKoZIhvcNAQEFBQADggEB
406
+ AGKhgByl/ur6SBFFKJcISJONFRaxf2ji0l6ut9XO1H2BSOSRjUbsFDWdWZG+D24Q
407
+ JKKseSWPWAC5uHq00sBWkvmtip+duESPeDEdumdBhdiUUgGamW2Ew2y4yAdAVDeG
408
+ t1p2fs8SylQN6AMTG/+R+MGHxhvg+UELYLcvAjjcDW2VhDQaJ1eFEfcMW1zRtvvh
409
+ LJmVErouwFKyAjwhbF6sNxmToSnbO1ciWwIILMsOBNHMETCp+SzkRDIRWIkm6m+q
410
+ RwRyYoHysODGvnu8VXS1hGRr2GIxeBga7dAGa2VLE/iUQ0d4lEskYU+6C4ZLyAWF
411
+ O89dvLNRzpL10MaWCYVREks=
412
+ -----END CERTIFICATE-----
413
+ __DIST_CERT__
414
+ end
415
+
416
+
417
+ end
@@ -0,0 +1,136 @@
1
+ # HTTPClient - HTTP client library.
2
+ # Copyright (C) 2000-2009 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
+ end
61
+
62
+ # Registers new timeout period.
63
+ def register(thread, sec, ex)
64
+ period = Period.new(thread, Time.now + sec, ex || ::Timeout::Error)
65
+ @pool[period] = true
66
+ if @next.nil? or period.time < @next
67
+ begin
68
+ @thread.wakeup
69
+ rescue ThreadError
70
+ # Thread may be dead by fork.
71
+ @thread = start_timer_thread
72
+ end
73
+ end
74
+ period
75
+ end
76
+
77
+ # Cancels the given period.
78
+ def cancel(period)
79
+ @pool.delete(period)
80
+ period.cancel
81
+ end
82
+
83
+ private
84
+
85
+ def start_timer_thread
86
+ thread = Thread.new {
87
+ while true
88
+ if @pool.empty?
89
+ @next = nil
90
+ sleep
91
+ else
92
+ min, = @pool.min { |a, b| a[0].time <=> b[0].time }
93
+ @next = min.time
94
+ sec = @next - Time.now
95
+ if sec > 0
96
+ sleep(sec)
97
+ end
98
+ end
99
+ now = Time.now
100
+ @pool.keys.each do |period|
101
+ if period.time < now
102
+ period.raise('execution expired')
103
+ cancel(period)
104
+ end
105
+ end
106
+ end
107
+ }
108
+ Thread.pass while thread.status != 'sleep'
109
+ thread
110
+ end
111
+ end
112
+
113
+ class << self
114
+ # CAUTION: caller must aware of race condition.
115
+ def timeout_scheduler
116
+ @timeout_scheduler ||= TimeoutScheduler.new
117
+ end
118
+ end
119
+ timeout_scheduler # initialize at first time.
120
+
121
+ module Timeout
122
+ def timeout(sec, ex = nil, &block)
123
+ return yield if sec == nil or sec.zero?
124
+ scheduler = nil
125
+ begin
126
+ scheduler = HTTPClient.timeout_scheduler
127
+ period = scheduler.register(Thread.current, sec, ex)
128
+ yield(sec)
129
+ ensure
130
+ scheduler.cancel(period) if scheduler and period
131
+ end
132
+ end
133
+ end
134
+
135
+
136
+ end