httpclient-jgraichen 2.3.4.2
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/README.txt +759 -0
- data/bin/httpclient +65 -0
- data/lib/hexdump.rb +50 -0
- data/lib/http-access2.rb +55 -0
- data/lib/http-access2/cookie.rb +1 -0
- data/lib/http-access2/http.rb +1 -0
- data/lib/httpclient.rb +1156 -0
- data/lib/httpclient/auth.rb +899 -0
- data/lib/httpclient/cacert.p7s +1912 -0
- data/lib/httpclient/connection.rb +88 -0
- data/lib/httpclient/cookie.rb +438 -0
- data/lib/httpclient/http.rb +1046 -0
- data/lib/httpclient/include_client.rb +83 -0
- data/lib/httpclient/session.rb +1028 -0
- data/lib/httpclient/ssl_config.rb +405 -0
- data/lib/httpclient/timeout.rb +140 -0
- data/lib/httpclient/util.rb +178 -0
- data/lib/httpclient/version.rb +3 -0
- data/lib/oauthclient.rb +110 -0
- data/sample/async.rb +8 -0
- data/sample/auth.rb +11 -0
- data/sample/cookie.rb +18 -0
- data/sample/dav.rb +103 -0
- data/sample/howto.rb +49 -0
- data/sample/oauth_buzz.rb +57 -0
- data/sample/oauth_friendfeed.rb +59 -0
- data/sample/oauth_twitter.rb +61 -0
- data/sample/ssl/0cert.pem +22 -0
- data/sample/ssl/0key.pem +30 -0
- data/sample/ssl/1000cert.pem +19 -0
- data/sample/ssl/1000key.pem +18 -0
- data/sample/ssl/htdocs/index.html +10 -0
- data/sample/ssl/ssl_client.rb +22 -0
- data/sample/ssl/webrick_httpsd.rb +29 -0
- data/sample/stream.rb +21 -0
- data/sample/thread.rb +27 -0
- data/sample/wcat.rb +21 -0
- data/test/ca-chain.cert +44 -0
- data/test/ca.cert +23 -0
- data/test/client.cert +19 -0
- data/test/client.key +15 -0
- data/test/helper.rb +129 -0
- data/test/htdigest +1 -0
- data/test/htpasswd +2 -0
- data/test/runner.rb +2 -0
- data/test/server.cert +19 -0
- data/test/server.key +15 -0
- data/test/sslsvr.rb +65 -0
- data/test/subca.cert +21 -0
- data/test/test_auth.rb +348 -0
- data/test/test_cookie.rb +412 -0
- data/test/test_hexdump.rb +14 -0
- data/test/test_http-access2.rb +507 -0
- data/test/test_httpclient.rb +1783 -0
- data/test/test_include_client.rb +52 -0
- data/test/test_ssl.rb +235 -0
- metadata +100 -0
@@ -0,0 +1,405 @@
|
|
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 add_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. Regardless its
|
30
|
+
# filename extension (p7s), HTTPClient doesn't verify the signature in it.
|
31
|
+
#
|
32
|
+
# You may want to change trust anchor by yourself. Call clear_cert_store
|
33
|
+
# then add_trust_ca for that purpose.
|
34
|
+
class SSLConfig
|
35
|
+
include OpenSSL if SSLEnabled
|
36
|
+
|
37
|
+
# String name of OpenSSL's SSL version method name: TLSv1_2, TLSv1_1, TLSv1,
|
38
|
+
# SSLv2, SSLv23, SSLv3 or nil to allow version negotiation (default).
|
39
|
+
# See {OpenSSL::SSL::SSLContext::METHODS}.
|
40
|
+
attr_reader :ssl_version
|
41
|
+
# OpenSSL::X509::Certificate:: certificate for SSL client authenticateion.
|
42
|
+
# nil by default. (no client authenticateion)
|
43
|
+
attr_reader :client_cert
|
44
|
+
# OpenSSL::PKey::PKey:: private key for SSL client authentication.
|
45
|
+
# nil by default. (no client authenticateion)
|
46
|
+
attr_reader :client_key
|
47
|
+
|
48
|
+
# A number which represents OpenSSL's verify mode. Default value is
|
49
|
+
# OpenSSL::SSL::VERIFY_PEER | OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT.
|
50
|
+
attr_reader :verify_mode
|
51
|
+
# A number of verify depth. Certification path which length is longer than
|
52
|
+
# this depth is not allowed.
|
53
|
+
attr_reader :verify_depth
|
54
|
+
# A callback handler for custom certificate verification. nil by default.
|
55
|
+
# If the handler is set, handler.call is invoked just after general
|
56
|
+
# OpenSSL's verification. handler.call is invoked with 2 arguments,
|
57
|
+
# ok and ctx; ok is a result of general OpenSSL's verification. ctx is a
|
58
|
+
# OpenSSL::X509::StoreContext.
|
59
|
+
attr_reader :verify_callback
|
60
|
+
# SSL timeout in sec. nil by default.
|
61
|
+
attr_reader :timeout
|
62
|
+
# A number of OpenSSL's SSL options. Default value is
|
63
|
+
# OpenSSL::SSL::OP_ALL | OpenSSL::SSL::OP_NO_SSLv2
|
64
|
+
attr_reader :options
|
65
|
+
# A String of OpenSSL's cipher configuration. Default value is
|
66
|
+
# ALL:!ADH:!LOW:!EXP:!MD5:+SSLv2:@STRENGTH
|
67
|
+
# See ciphers(1) man in OpenSSL for more detail.
|
68
|
+
attr_reader :ciphers
|
69
|
+
|
70
|
+
# OpenSSL::X509::X509::Store used for verification. You can reset the
|
71
|
+
# store with clear_cert_store and set the new store with cert_store=.
|
72
|
+
attr_reader :cert_store # don't use if you don't know what it is.
|
73
|
+
|
74
|
+
# For server side configuration. Ignore this.
|
75
|
+
attr_reader :client_ca # :nodoc:
|
76
|
+
|
77
|
+
# Creates a SSLConfig.
|
78
|
+
def initialize(client)
|
79
|
+
return unless SSLEnabled
|
80
|
+
@client = client
|
81
|
+
@cert_store = X509::Store.new
|
82
|
+
@client_cert = @client_key = @client_ca = nil
|
83
|
+
@verify_mode = SSL::VERIFY_PEER | SSL::VERIFY_FAIL_IF_NO_PEER_CERT
|
84
|
+
@verify_depth = nil
|
85
|
+
@verify_callback = nil
|
86
|
+
@dest = nil
|
87
|
+
@timeout = nil
|
88
|
+
@ssl_version = nil
|
89
|
+
@options = defined?(SSL::OP_ALL) ? SSL::OP_ALL | SSL::OP_NO_SSLv2 : nil
|
90
|
+
# OpenSSL 0.9.8 default: "ALL:!ADH:!LOW:!EXP:!MD5:+SSLv2:@STRENGTH"
|
91
|
+
@ciphers = "ALL:!aNULL:!eNULL:!SSLv2" # OpenSSL >1.0.0 default
|
92
|
+
@cacerts_loaded = false
|
93
|
+
end
|
94
|
+
|
95
|
+
# Sets SSL version method String. Possible values: "SSLv2" for SSL2,
|
96
|
+
# "SSLv3" for SSL3 and TLS1.x, "SSLv23" for SSL3 with fallback to SSL2.
|
97
|
+
def ssl_version=(ssl_version)
|
98
|
+
@ssl_version = ssl_version
|
99
|
+
change_notify
|
100
|
+
end
|
101
|
+
|
102
|
+
# Sets certificate (OpenSSL::X509::Certificate) for SSL client
|
103
|
+
# authentication.
|
104
|
+
# client_key and client_cert must be a pair.
|
105
|
+
#
|
106
|
+
# Calling this method resets all existing sessions.
|
107
|
+
def client_cert=(client_cert)
|
108
|
+
@client_cert = client_cert
|
109
|
+
change_notify
|
110
|
+
end
|
111
|
+
|
112
|
+
# Sets private key (OpenSSL::PKey::PKey) for SSL client authentication.
|
113
|
+
# client_key and client_cert must be a pair.
|
114
|
+
#
|
115
|
+
# Calling this method resets all existing sessions.
|
116
|
+
def client_key=(client_key)
|
117
|
+
@client_key = client_key
|
118
|
+
change_notify
|
119
|
+
end
|
120
|
+
|
121
|
+
# Sets certificate and private key for SSL client authentication.
|
122
|
+
# cert_file:: must be a filename of PEM/DER formatted file.
|
123
|
+
# key_file:: must be a filename of PEM/DER formatted file. Key must be an
|
124
|
+
# RSA key. If you want to use other PKey algorithm,
|
125
|
+
# use client_key=.
|
126
|
+
#
|
127
|
+
# Calling this method resets all existing sessions.
|
128
|
+
def set_client_cert_file(cert_file, key_file)
|
129
|
+
@client_cert = X509::Certificate.new(File.open(cert_file) { |f| f.read })
|
130
|
+
@client_key = PKey::RSA.new(File.open(key_file) { |f| f.read })
|
131
|
+
change_notify
|
132
|
+
end
|
133
|
+
|
134
|
+
# Sets OpenSSL's default trusted CA certificates. Generally, OpenSSL is
|
135
|
+
# configured to use OS's trusted CA certificates located at
|
136
|
+
# /etc/pki/certs or /etc/ssl/certs. Unfortunately OpenSSL's Windows build
|
137
|
+
# does not work with Windows Certificate Storage.
|
138
|
+
#
|
139
|
+
# On Windows or when you build OpenSSL manually, you can set the
|
140
|
+
# CA certificates directory by SSL_CERT_DIR env variable at runtime.
|
141
|
+
#
|
142
|
+
# SSL_CERT_DIR=/etc/ssl/certs ruby -rhttpclient -e "..."
|
143
|
+
#
|
144
|
+
# Calling this method resets all existing sessions.
|
145
|
+
def set_default_paths
|
146
|
+
@cacerts_loaded = true # avoid lazy override
|
147
|
+
@cert_store = X509::Store.new
|
148
|
+
@cert_store.set_default_paths
|
149
|
+
change_notify
|
150
|
+
end
|
151
|
+
|
152
|
+
# Drops current certificate store (OpenSSL::X509::Store) for SSL and create
|
153
|
+
# new one for the next session.
|
154
|
+
#
|
155
|
+
# Calling this method resets all existing sessions.
|
156
|
+
def clear_cert_store
|
157
|
+
@cacerts_loaded = true # avoid lazy override
|
158
|
+
@cert_store = X509::Store.new
|
159
|
+
change_notify
|
160
|
+
end
|
161
|
+
|
162
|
+
# Sets new certificate store (OpenSSL::X509::Store).
|
163
|
+
# don't use if you don't know what it is.
|
164
|
+
#
|
165
|
+
# Calling this method resets all existing sessions.
|
166
|
+
def cert_store=(cert_store)
|
167
|
+
@cacerts_loaded = true # avoid lazy override
|
168
|
+
@cert_store = cert_store
|
169
|
+
change_notify
|
170
|
+
end
|
171
|
+
|
172
|
+
# Sets trust anchor certificate(s) for verification.
|
173
|
+
# trust_ca_file_or_hashed_dir:: a filename of a PEM/DER formatted
|
174
|
+
# OpenSSL::X509::Certificate or
|
175
|
+
# a 'c-rehash'eddirectory name which stores
|
176
|
+
# trusted certificate files.
|
177
|
+
#
|
178
|
+
# Calling this method resets all existing sessions.
|
179
|
+
def add_trust_ca(trust_ca_file_or_hashed_dir)
|
180
|
+
@cacerts_loaded = true # avoid lazy override
|
181
|
+
add_trust_ca_to_store(@cert_store, trust_ca_file_or_hashed_dir)
|
182
|
+
change_notify
|
183
|
+
end
|
184
|
+
alias set_trust_ca add_trust_ca
|
185
|
+
|
186
|
+
def add_trust_ca_to_store(cert_store, trust_ca_file_or_hashed_dir)
|
187
|
+
if FileTest.directory?(trust_ca_file_or_hashed_dir)
|
188
|
+
cert_store.add_path(trust_ca_file_or_hashed_dir)
|
189
|
+
else
|
190
|
+
cert_store.add_file(trust_ca_file_or_hashed_dir)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
# Loads default trust anchors.
|
195
|
+
# Calling this method resets all existing sessions.
|
196
|
+
def load_trust_ca
|
197
|
+
load_cacerts(@cert_store)
|
198
|
+
change_notify
|
199
|
+
end
|
200
|
+
|
201
|
+
# Adds CRL for verification.
|
202
|
+
# crl:: a OpenSSL::X509::CRL or a filename of a PEM/DER formatted
|
203
|
+
# OpenSSL::X509::CRL.
|
204
|
+
#
|
205
|
+
# Calling this method resets all existing sessions.
|
206
|
+
def add_crl(crl)
|
207
|
+
unless crl.is_a?(X509::CRL)
|
208
|
+
crl = X509::CRL.new(File.open(crl) { |f| f.read })
|
209
|
+
end
|
210
|
+
@cert_store.add_crl(crl)
|
211
|
+
@cert_store.flags = X509::V_FLAG_CRL_CHECK | X509::V_FLAG_CRL_CHECK_ALL
|
212
|
+
change_notify
|
213
|
+
end
|
214
|
+
alias set_crl add_crl
|
215
|
+
|
216
|
+
# Sets verify mode of OpenSSL. New value must be a combination of
|
217
|
+
# constants OpenSSL::SSL::VERIFY_*
|
218
|
+
#
|
219
|
+
# Calling this method resets all existing sessions.
|
220
|
+
def verify_mode=(verify_mode)
|
221
|
+
@verify_mode = verify_mode
|
222
|
+
change_notify
|
223
|
+
end
|
224
|
+
|
225
|
+
# Sets verify depth. New value must be a number.
|
226
|
+
#
|
227
|
+
# Calling this method resets all existing sessions.
|
228
|
+
def verify_depth=(verify_depth)
|
229
|
+
@verify_depth = verify_depth
|
230
|
+
change_notify
|
231
|
+
end
|
232
|
+
|
233
|
+
# Sets callback handler for custom certificate verification.
|
234
|
+
# See verify_callback.
|
235
|
+
#
|
236
|
+
# Calling this method resets all existing sessions.
|
237
|
+
def verify_callback=(verify_callback)
|
238
|
+
@verify_callback = verify_callback
|
239
|
+
change_notify
|
240
|
+
end
|
241
|
+
|
242
|
+
# Sets SSL timeout in sec.
|
243
|
+
#
|
244
|
+
# Calling this method resets all existing sessions.
|
245
|
+
def timeout=(timeout)
|
246
|
+
@timeout = timeout
|
247
|
+
change_notify
|
248
|
+
end
|
249
|
+
|
250
|
+
# Sets SSL options. New value must be a combination of # constants
|
251
|
+
# OpenSSL::SSL::OP_*
|
252
|
+
#
|
253
|
+
# Calling this method resets all existing sessions.
|
254
|
+
def options=(options)
|
255
|
+
@options = options
|
256
|
+
change_notify
|
257
|
+
end
|
258
|
+
|
259
|
+
# Sets cipher configuration. New value must be a String.
|
260
|
+
#
|
261
|
+
# Calling this method resets all existing sessions.
|
262
|
+
def ciphers=(ciphers)
|
263
|
+
@ciphers = ciphers
|
264
|
+
change_notify
|
265
|
+
end
|
266
|
+
|
267
|
+
def client_ca=(client_ca) # :nodoc:
|
268
|
+
@client_ca = client_ca
|
269
|
+
change_notify
|
270
|
+
end
|
271
|
+
|
272
|
+
# interfaces for SSLSocketWrap.
|
273
|
+
def set_context(ctx) # :nodoc:
|
274
|
+
load_trust_ca unless @cacerts_loaded
|
275
|
+
@cacerts_loaded = true
|
276
|
+
# Verification: Use Store#verify_callback instead of SSLContext#verify*?
|
277
|
+
ctx.cert_store = @cert_store
|
278
|
+
ctx.verify_mode = @verify_mode
|
279
|
+
ctx.verify_depth = @verify_depth if @verify_depth
|
280
|
+
ctx.verify_callback = @verify_callback || method(:default_verify_callback)
|
281
|
+
# SSL config
|
282
|
+
ctx.cert = @client_cert
|
283
|
+
ctx.key = @client_key
|
284
|
+
ctx.client_ca = @client_ca
|
285
|
+
ctx.timeout = @timeout
|
286
|
+
ctx.options = @options
|
287
|
+
ctx.ciphers = @ciphers
|
288
|
+
ctx.ssl_version = @ssl_version if @ssl_version
|
289
|
+
end
|
290
|
+
|
291
|
+
# post connection check proc for ruby < 1.8.5.
|
292
|
+
# this definition must match with the one in ext/openssl/lib/openssl/ssl.rb
|
293
|
+
def post_connection_check(peer_cert, hostname) # :nodoc:
|
294
|
+
check_common_name = true
|
295
|
+
cert = peer_cert
|
296
|
+
cert.extensions.each{|ext|
|
297
|
+
next if ext.oid != "subjectAltName"
|
298
|
+
ext.value.split(/,\s+/).each{|general_name|
|
299
|
+
if /\ADNS:(.*)/ =~ general_name
|
300
|
+
check_common_name = false
|
301
|
+
reg = Regexp.escape($1).gsub(/\\\*/, "[^.]+")
|
302
|
+
return true if /\A#{reg}\z/i =~ hostname
|
303
|
+
elsif /\AIP Address:(.*)/ =~ general_name
|
304
|
+
check_common_name = false
|
305
|
+
return true if $1 == hostname
|
306
|
+
end
|
307
|
+
}
|
308
|
+
}
|
309
|
+
if check_common_name
|
310
|
+
cert.subject.to_a.each{|oid, value|
|
311
|
+
if oid == "CN"
|
312
|
+
reg = Regexp.escape(value).gsub(/\\\*/, "[^.]+")
|
313
|
+
return true if /\A#{reg}\z/i =~ hostname
|
314
|
+
end
|
315
|
+
}
|
316
|
+
end
|
317
|
+
raise SSL::SSLError, "hostname was not match with the server certificate"
|
318
|
+
end
|
319
|
+
|
320
|
+
# Default callback for verification: only dumps error.
|
321
|
+
def default_verify_callback(is_ok, ctx)
|
322
|
+
if $DEBUG
|
323
|
+
if is_ok
|
324
|
+
warn("ok: #{ctx.current_cert.subject.to_s.dump}")
|
325
|
+
else
|
326
|
+
warn("ng: #{ctx.current_cert.subject.to_s.dump} at depth #{ctx.error_depth} - #{ctx.error}: #{ctx.error_string} in #{ctx.chain.inspect}")
|
327
|
+
end
|
328
|
+
warn(ctx.current_cert.to_text)
|
329
|
+
warn(ctx.current_cert.to_pem)
|
330
|
+
end
|
331
|
+
if !is_ok
|
332
|
+
depth = ctx.error_depth
|
333
|
+
code = ctx.error
|
334
|
+
msg = ctx.error_string
|
335
|
+
warn("at depth #{depth} - #{code}: #{msg}")
|
336
|
+
end
|
337
|
+
is_ok
|
338
|
+
end
|
339
|
+
|
340
|
+
# Sample callback method: CAUTION: does not check CRL/ARL.
|
341
|
+
def sample_verify_callback(is_ok, ctx)
|
342
|
+
unless is_ok
|
343
|
+
depth = ctx.error_depth
|
344
|
+
code = ctx.error
|
345
|
+
msg = ctx.error_string
|
346
|
+
warn("at depth #{depth} - #{code}: #{msg}") if $DEBUG
|
347
|
+
return false
|
348
|
+
end
|
349
|
+
|
350
|
+
cert = ctx.current_cert
|
351
|
+
self_signed = false
|
352
|
+
ca = false
|
353
|
+
pathlen = nil
|
354
|
+
server_auth = true
|
355
|
+
self_signed = (cert.subject.cmp(cert.issuer) == 0)
|
356
|
+
|
357
|
+
# Check extensions whatever its criticality is. (sample)
|
358
|
+
cert.extensions.each do |ex|
|
359
|
+
case ex.oid
|
360
|
+
when 'basicConstraints'
|
361
|
+
/CA:(TRUE|FALSE), pathlen:(\d+)/ =~ ex.value
|
362
|
+
ca = ($1 == 'TRUE')
|
363
|
+
pathlen = $2.to_i
|
364
|
+
when 'keyUsage'
|
365
|
+
usage = ex.value.split(/\s*,\s*/)
|
366
|
+
ca = usage.include?('Certificate Sign')
|
367
|
+
server_auth = usage.include?('Key Encipherment')
|
368
|
+
when 'extendedKeyUsage'
|
369
|
+
usage = ex.value.split(/\s*,\s*/)
|
370
|
+
server_auth = usage.include?('Netscape Server Gated Crypto')
|
371
|
+
when 'nsCertType'
|
372
|
+
usage = ex.value.split(/\s*,\s*/)
|
373
|
+
ca = usage.include?('SSL CA')
|
374
|
+
server_auth = usage.include?('SSL Server')
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
378
|
+
if self_signed
|
379
|
+
warn('self signing CA') if $DEBUG
|
380
|
+
return true
|
381
|
+
elsif ca
|
382
|
+
warn('middle level CA') if $DEBUG
|
383
|
+
return true
|
384
|
+
elsif server_auth
|
385
|
+
warn('for server authentication') if $DEBUG
|
386
|
+
return true
|
387
|
+
end
|
388
|
+
|
389
|
+
return false
|
390
|
+
end
|
391
|
+
|
392
|
+
private
|
393
|
+
|
394
|
+
def change_notify
|
395
|
+
@client.reset_all
|
396
|
+
end
|
397
|
+
|
398
|
+
def load_cacerts(cert_store)
|
399
|
+
file = File.join(File.dirname(__FILE__), 'cacert.p7s')
|
400
|
+
add_trust_ca_to_store(cert_store, file)
|
401
|
+
end
|
402
|
+
end
|
403
|
+
|
404
|
+
|
405
|
+
end
|
@@ -0,0 +1,140 @@
|
|
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
|
+
if !defined?(JRUBY_VERSION) and RUBY_VERSION < '1.9'
|
27
|
+
class TimeoutScheduler
|
28
|
+
|
29
|
+
# Represents timeout period.
|
30
|
+
class Period
|
31
|
+
attr_reader :thread, :time
|
32
|
+
|
33
|
+
# Creates new Period.
|
34
|
+
def initialize(thread, time, ex)
|
35
|
+
@thread, @time, @ex = thread, time, ex
|
36
|
+
@lock = Mutex.new
|
37
|
+
end
|
38
|
+
|
39
|
+
# Raises if thread exists and alive.
|
40
|
+
def raise(message)
|
41
|
+
@lock.synchronize do
|
42
|
+
if @thread and @thread.alive?
|
43
|
+
@thread.raise(@ex, message)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Cancel this Period. Mutex is needed to avoid too-late exception.
|
49
|
+
def cancel
|
50
|
+
@lock.synchronize do
|
51
|
+
@thread = nil
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Creates new TimeoutScheduler.
|
57
|
+
def initialize
|
58
|
+
@pool = {}
|
59
|
+
@next = nil
|
60
|
+
@thread = start_timer_thread
|
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
|
+
begin
|
69
|
+
@thread.wakeup
|
70
|
+
rescue ThreadError
|
71
|
+
# Thread may be dead by fork.
|
72
|
+
@thread = start_timer_thread
|
73
|
+
end
|
74
|
+
end
|
75
|
+
period
|
76
|
+
end
|
77
|
+
|
78
|
+
# Cancels the given period.
|
79
|
+
def cancel(period)
|
80
|
+
@pool.delete(period)
|
81
|
+
period.cancel
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
def start_timer_thread
|
87
|
+
thread = Thread.new {
|
88
|
+
while true
|
89
|
+
if @pool.empty?
|
90
|
+
@next = nil
|
91
|
+
sleep
|
92
|
+
else
|
93
|
+
min, = @pool.min { |a, b| a[0].time <=> b[0].time }
|
94
|
+
@next = min.time
|
95
|
+
sec = @next - Time.now
|
96
|
+
if sec > 0
|
97
|
+
sleep(sec)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
now = Time.now
|
101
|
+
@pool.keys.each do |period|
|
102
|
+
if period.time < now
|
103
|
+
period.raise('execution expired')
|
104
|
+
cancel(period)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
}
|
109
|
+
Thread.pass while thread.status != 'sleep'
|
110
|
+
thread
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
class << self
|
115
|
+
# CAUTION: caller must aware of race condition.
|
116
|
+
def timeout_scheduler
|
117
|
+
@timeout_scheduler ||= TimeoutScheduler.new
|
118
|
+
end
|
119
|
+
end
|
120
|
+
timeout_scheduler # initialize at first time.
|
121
|
+
end
|
122
|
+
|
123
|
+
module Timeout
|
124
|
+
if !defined?(JRUBY_VERSION) and RUBY_VERSION < '1.9'
|
125
|
+
def timeout(sec, ex = nil, &block)
|
126
|
+
return yield if sec == nil or sec.zero?
|
127
|
+
scheduler = nil
|
128
|
+
begin
|
129
|
+
scheduler = HTTPClient.timeout_scheduler
|
130
|
+
period = scheduler.register(Thread.current, sec, ex)
|
131
|
+
yield(sec)
|
132
|
+
ensure
|
133
|
+
scheduler.cancel(period) if scheduler and period
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
|
140
|
+
end
|