vmc 0.0.4 → 0.0.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.
@@ -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).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
+ [
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
@@ -0,0 +1,86 @@
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 'uri'
10
+
11
+
12
+ class HTTPClient
13
+
14
+
15
+ # A module for common function.
16
+ module Util
17
+ # Keyword argument helper.
18
+ # args:: given arguments.
19
+ # *field:: a list of arguments to be extracted.
20
+ #
21
+ # You can extract 3 arguments (a, b, c) with:
22
+ #
23
+ # include Util
24
+ # def my_method(*args)
25
+ # a, b, c = keyword_argument(args, :a, :b, :c)
26
+ # ...
27
+ # end
28
+ # my_method(1, 2, 3)
29
+ # my_method(:b => 2, :a = 1)
30
+ #
31
+ # instead of;
32
+ #
33
+ # def my_method(a, b, c)
34
+ # ...
35
+ # end
36
+ #
37
+ def keyword_argument(args, *field)
38
+ if args.size == 1 and args[0].is_a?(Hash)
39
+ args[0].values_at(*field)
40
+ else
41
+ args
42
+ end
43
+ end
44
+
45
+ # Gets an URI instance.
46
+ def urify(uri)
47
+ if uri.nil?
48
+ nil
49
+ elsif uri.is_a?(URI)
50
+ uri
51
+ else
52
+ URI.parse(uri.to_s)
53
+ end
54
+ end
55
+
56
+ # Returns true if the given 2 URIs have a part_of relationship.
57
+ # * the same scheme
58
+ # * the same host String (no host resolution or IP-addr conversion)
59
+ # * the same port number
60
+ # * target URI's path starts with base URI's path.
61
+ def uri_part_of(uri, part)
62
+ ((uri.scheme == part.scheme) and
63
+ (uri.host == part.host) and
64
+ (uri.port == part.port) and
65
+ uri.path.upcase.index(part.path.upcase) == 0)
66
+ end
67
+ module_function :uri_part_of
68
+
69
+ # Returns parent directory URI of the given URI.
70
+ def uri_dirname(uri)
71
+ uri = uri.clone
72
+ uri.path = uri.path.sub(/\/[^\/]*\z/, '/')
73
+ uri
74
+ end
75
+ module_function :uri_dirname
76
+
77
+ # Finds a value of a Hash.
78
+ def hash_find_value(hash, &block)
79
+ v = hash.find(&block)
80
+ v ? v[1] : nil
81
+ end
82
+ module_function :hash_find_value
83
+ end
84
+
85
+
86
+ end