httpclient 2.7.2 → 2.9.0
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 +5 -5
- data/bin/httpclient +2 -0
- data/lib/hexdump.rb +12 -12
- data/lib/httpclient/http.rb +12 -9
- data/lib/httpclient/jruby_ssl_socket.rb +119 -52
- data/lib/httpclient/session.rb +37 -18
- data/lib/httpclient/ssl_config.rb +90 -122
- data/lib/httpclient/ssl_socket.rb +26 -24
- data/lib/httpclient/util.rb +1 -1
- data/lib/httpclient/version.rb +1 -1
- data/lib/httpclient/webagent-cookie.rb +1 -1
- data/lib/httpclient.rb +19 -10
- data/lib/jsonclient.rb +8 -5
- data/lib/oauthclient.rb +1 -0
- data/sample/auth.rb +1 -1
- data/sample/generate_test_keys.rb +99 -0
- data/test/ca-chain.pem +32 -36
- data/test/ca.cert +16 -19
- data/test/ca.key +27 -0
- data/test/ca.srl +1 -0
- data/test/client-pass.key +30 -18
- data/test/client.cert +17 -16
- data/test/client.key +25 -13
- data/test/fixtures/verify.alt.cert +20 -0
- data/test/fixtures/verify.foo.cert +20 -0
- data/test/fixtures/verify.key +27 -0
- data/test/fixtures/verify.localhost.cert +20 -0
- data/test/helper.rb +5 -7
- data/test/jruby_ssl_socket/test_pemutils.rb +32 -0
- data/test/server.cert +16 -15
- data/test/server.key +25 -13
- data/test/subca.cert +16 -17
- data/test/subca.key +27 -0
- data/test/subca.srl +1 -0
- data/test/test_auth.rb +21 -18
- data/test/test_hexdump.rb +1 -2
- data/test/test_http-access2.rb +33 -23
- data/test/test_httpclient.rb +133 -58
- data/test/test_jsonclient.rb +18 -0
- data/test/test_ssl.rb +205 -23
- metadata +32 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: e1df0b78f53d502000683d048be1316f156247e5e17995f5fb04a125302bdb4e
|
4
|
+
data.tar.gz: d533eeaf2333f2f35820441a74cdf84075e5227d4fb0648f4e2ea4d2099bd511
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 94024e0c5b5bd08800d9b3989151ab919869fabcaed65e4a375490fde0f6ea3b5bfc3ad4a5a9803bb057c5b8d36efc10c6ae5ade8a974c665d443730c63bc7ba
|
7
|
+
data.tar.gz: 768ae9ad96a73740675aca861a81075af3164a4b8875f81368a3228ecfdf523fe6c13cd9355bbe8ddd9155031ff1f908d8ec85e1bd112e0cd31d8b26d809dd6a
|
data/bin/httpclient
CHANGED
@@ -20,6 +20,7 @@ end
|
|
20
20
|
url = ARGV.shift
|
21
21
|
if method && url
|
22
22
|
client = HTTPClient.new
|
23
|
+
client.strict_response_size_check = true
|
23
24
|
if method == 'download'
|
24
25
|
print client.get_content(url)
|
25
26
|
else
|
@@ -37,6 +38,7 @@ require 'irb/completion'
|
|
37
38
|
class Runner
|
38
39
|
def initialize
|
39
40
|
@httpclient = HTTPClient.new
|
41
|
+
@httpclient.strict_response_size_check = true
|
40
42
|
end
|
41
43
|
|
42
44
|
def method_missing(msg, *a, &b)
|
data/lib/hexdump.rb
CHANGED
@@ -11,13 +11,13 @@ module HexDump
|
|
11
11
|
result = []
|
12
12
|
while raw = str.slice(offset, 16) and raw.length > 0
|
13
13
|
# data field
|
14
|
-
data = ''
|
14
|
+
data = ''.dup
|
15
15
|
for v in raw.unpack('N* a*')
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
16
|
+
if v.kind_of? Integer
|
17
|
+
data << sprintf("%08x ", v)
|
18
|
+
else
|
19
|
+
v.each_byte {|c| data << sprintf("%02x", c) }
|
20
|
+
end
|
21
21
|
end
|
22
22
|
# text field
|
23
23
|
text = raw.tr("\000-\037\177-\377", ".")
|
@@ -25,12 +25,12 @@ module HexDump
|
|
25
25
|
offset += 16
|
26
26
|
# omit duplicate line
|
27
27
|
if /^(#{regex_quote_n(raw)})+/n =~ str[offset .. -1]
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
28
|
+
result << sprintf("%08x ...", offset)
|
29
|
+
offset += $&.length
|
30
|
+
# should print at the end
|
31
|
+
if offset == str.length
|
32
|
+
result << sprintf("%08x %-36s %s", offset-16, data, text)
|
33
|
+
end
|
34
34
|
end
|
35
35
|
end
|
36
36
|
result
|
data/lib/httpclient/http.rb
CHANGED
@@ -238,7 +238,7 @@ module HTTP
|
|
238
238
|
if defined?(Encoding::ASCII_8BIT)
|
239
239
|
def set_body_encoding
|
240
240
|
if type = self.content_type
|
241
|
-
OpenURI::Meta.init(o = '')
|
241
|
+
OpenURI::Meta.init(o = ''.dup)
|
242
242
|
o.meta_add_field('content-type', type)
|
243
243
|
@body_encoding = o.encoding
|
244
244
|
end
|
@@ -491,7 +491,7 @@ module HTTP
|
|
491
491
|
# String.
|
492
492
|
#
|
493
493
|
# assert: @size is not nil
|
494
|
-
def dump(header = '', dev = '')
|
494
|
+
def dump(header = '', dev = ''.dup)
|
495
495
|
if @body.is_a?(Parts)
|
496
496
|
dev << header
|
497
497
|
@body.parts.each do |part|
|
@@ -521,7 +521,7 @@ module HTTP
|
|
521
521
|
# reason. (header is dumped to dev, too)
|
522
522
|
# If no dev (the second argument) given, this method returns a dumped
|
523
523
|
# String.
|
524
|
-
def dump_chunked(header = '', dev = '')
|
524
|
+
def dump_chunked(header = '', dev = ''.dup)
|
525
525
|
dev << header
|
526
526
|
if @body.is_a?(Parts)
|
527
527
|
@body.parts.each do |part|
|
@@ -574,7 +574,7 @@ module HTTP
|
|
574
574
|
end
|
575
575
|
|
576
576
|
def dump_file(io, dev, sz)
|
577
|
-
buf = ''
|
577
|
+
buf = ''.dup
|
578
578
|
rest = sz
|
579
579
|
while rest > 0
|
580
580
|
n = io.read([rest, @chunk_size].min, buf)
|
@@ -585,7 +585,7 @@ module HTTP
|
|
585
585
|
end
|
586
586
|
|
587
587
|
def dump_chunks(io, dev)
|
588
|
-
buf = ''
|
588
|
+
buf = ''.dup
|
589
589
|
while !io.read(@chunk_size, buf).nil?
|
590
590
|
dev << dump_chunk(buf)
|
591
591
|
end
|
@@ -618,8 +618,8 @@ module HTTP
|
|
618
618
|
if Message.file?(part)
|
619
619
|
@as_stream = true
|
620
620
|
@body << part
|
621
|
-
if part.respond_to?(:
|
622
|
-
sz = part.
|
621
|
+
if part.respond_to?(:stat)
|
622
|
+
sz = part.stat.size
|
623
623
|
add_size(part, sz)
|
624
624
|
elsif part.respond_to?(:size)
|
625
625
|
if sz = part.size
|
@@ -700,8 +700,9 @@ module HTTP
|
|
700
700
|
|
701
701
|
def params_from_file(value)
|
702
702
|
params = {}
|
703
|
+
original_filename = value.respond_to?(:original_filename) ? value.original_filename : nil
|
703
704
|
path = value.respond_to?(:path) ? value.path : nil
|
704
|
-
params['filename'] = File.basename(path || '')
|
705
|
+
params['filename'] = original_filename || File.basename(path || '')
|
705
706
|
# Creation time is not available from File::Stat
|
706
707
|
if value.respond_to?(:mtime)
|
707
708
|
params['modification-date'] = value.mtime.rfc822
|
@@ -808,6 +809,8 @@ module HTTP
|
|
808
809
|
case path
|
809
810
|
when /\.txt$/i
|
810
811
|
'text/plain'
|
812
|
+
when /\.xml$/i
|
813
|
+
'text/xml'
|
811
814
|
when /\.(htm|html)$/i
|
812
815
|
'text/html'
|
813
816
|
when /\.doc$/i
|
@@ -951,7 +954,7 @@ module HTTP
|
|
951
954
|
|
952
955
|
# Dumps message (header and body) to given dev.
|
953
956
|
# dev needs to respond to <<.
|
954
|
-
def dump(dev = '')
|
957
|
+
def dump(dev = ''.dup)
|
955
958
|
str = @http_header.dump + CRLF
|
956
959
|
if @http_header.chunked
|
957
960
|
dev = @http_body.dump_chunked(str, dev)
|
@@ -15,9 +15,27 @@ class HTTPClient
|
|
15
15
|
unless defined?(SSLSocket)
|
16
16
|
|
17
17
|
class JavaSocketWrap
|
18
|
+
java_import 'java.net.InetSocketAddress'
|
18
19
|
java_import 'java.io.BufferedInputStream'
|
20
|
+
|
19
21
|
BUF_SIZE = 1024 * 16
|
20
22
|
|
23
|
+
def self.normalize_timeout(timeout)
|
24
|
+
[Java::JavaLang::Integer::MAX_VALUE, timeout].min
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.connect(socket, site, opts = {})
|
28
|
+
socket_addr = InetSocketAddress.new(site.host, site.port)
|
29
|
+
if opts[:connect_timeout]
|
30
|
+
socket.connect(socket_addr, normalize_timeout(opts[:connect_timeout]))
|
31
|
+
else
|
32
|
+
socket.connect(socket_addr)
|
33
|
+
end
|
34
|
+
socket.setSoTimeout(normalize_timeout(opts[:so_timeout])) if opts[:so_timeout]
|
35
|
+
socket.setKeepAlive(true) if opts[:tcp_keepalive]
|
36
|
+
socket
|
37
|
+
end
|
38
|
+
|
21
39
|
def initialize(socket, debug_dev = nil)
|
22
40
|
@socket = socket
|
23
41
|
@debug_dev = debug_dev
|
@@ -39,7 +57,6 @@ unless defined?(SSLSocket)
|
|
39
57
|
@socket.isClosed
|
40
58
|
end
|
41
59
|
|
42
|
-
|
43
60
|
def gets(rs)
|
44
61
|
while (size = @bufstr.index(rs)).nil?
|
45
62
|
if fill() == -1
|
@@ -105,11 +122,15 @@ unless defined?(SSLSocket)
|
|
105
122
|
private
|
106
123
|
|
107
124
|
def fill
|
108
|
-
|
109
|
-
|
110
|
-
|
125
|
+
begin
|
126
|
+
size = @instr.read(@buf)
|
127
|
+
if size > 0
|
128
|
+
@bufstr << String.from_java_bytes(@buf, Encoding::BINARY)[0, size]
|
129
|
+
end
|
130
|
+
size
|
131
|
+
rescue java.io.IOException => e
|
132
|
+
raise OpenSSL::SSL::SSLError.new("#{e.class}: #{e.getMessage}")
|
111
133
|
end
|
112
|
-
size
|
113
134
|
end
|
114
135
|
|
115
136
|
def debug(str)
|
@@ -267,8 +288,8 @@ unless defined?(SSLSocket)
|
|
267
288
|
|
268
289
|
module PEMUtils
|
269
290
|
def self.read_certificate(pem)
|
270
|
-
|
271
|
-
der =
|
291
|
+
cert = pem.sub(/.*?-----BEGIN CERTIFICATE-----/m, '').sub(/-----END CERTIFICATE-----.*?/m, '')
|
292
|
+
der = cert.unpack('m*').first
|
272
293
|
cf = CertificateFactory.getInstance('X.509')
|
273
294
|
cf.generateCertificate(ByteArrayInputStream.new(der.to_java_bytes))
|
274
295
|
end
|
@@ -289,11 +310,11 @@ unless defined?(SSLSocket)
|
|
289
310
|
@keystore.load(nil)
|
290
311
|
end
|
291
312
|
|
292
|
-
def add(
|
293
|
-
cert_str =
|
313
|
+
def add(cert_source, key_source, password)
|
314
|
+
cert_str = cert_source.respond_to?(:to_pem) ? cert_source.to_pem : File.read(cert_source.to_s)
|
294
315
|
cert = PEMUtils.read_certificate(cert_str)
|
295
316
|
@keystore.setCertificateEntry('client_cert', cert)
|
296
|
-
key_str =
|
317
|
+
key_str = key_source.respond_to?(:to_pem) ? key_source.to_pem : File.read(key_source.to_s)
|
297
318
|
key_pair = PEMUtils.read_private_key(key_str, password)
|
298
319
|
@keystore.setKeyEntry('client_key', key_pair.getPrivate, PASSWORD, [cert].to_java(Certificate))
|
299
320
|
end
|
@@ -312,20 +333,23 @@ unless defined?(SSLSocket)
|
|
312
333
|
@size = 0
|
313
334
|
end
|
314
335
|
|
315
|
-
def add(
|
316
|
-
return if
|
317
|
-
if
|
318
|
-
|
336
|
+
def add(cert_source)
|
337
|
+
return if cert_source == :default
|
338
|
+
if cert_source.respond_to?(:to_pem)
|
339
|
+
pem = cert_source.to_pem
|
340
|
+
load_pem(pem)
|
341
|
+
elsif File.directory?(cert_source)
|
342
|
+
warn("#{cert_source}: directory not yet supported")
|
343
|
+
return
|
319
344
|
else
|
320
345
|
pem = nil
|
321
|
-
File.read(
|
346
|
+
File.read(cert_source).each_line do |line|
|
322
347
|
case line
|
323
348
|
when /-----BEGIN CERTIFICATE-----/
|
324
349
|
pem = ''
|
325
350
|
when /-----END CERTIFICATE-----/
|
326
|
-
|
327
|
-
|
328
|
-
@trust_store.setCertificateEntry("cert_#{@size}", cert)
|
351
|
+
load_pem(pem)
|
352
|
+
# keep parsing in case where multiple certificates in a file
|
329
353
|
else
|
330
354
|
if pem
|
331
355
|
pem << line
|
@@ -342,6 +366,14 @@ unless defined?(SSLSocket)
|
|
342
366
|
@trust_store
|
343
367
|
end
|
344
368
|
end
|
369
|
+
|
370
|
+
private
|
371
|
+
|
372
|
+
def load_pem(pem)
|
373
|
+
cert = PEMUtils.read_certificate(pem)
|
374
|
+
@size += 1
|
375
|
+
@trust_store.setCertificateEntry("cert_#{@size}", cert)
|
376
|
+
end
|
345
377
|
end
|
346
378
|
|
347
379
|
# Ported from commons-httpclient 'BrowserCompatHostnameVerifier'
|
@@ -429,26 +461,58 @@ unless defined?(SSLSocket)
|
|
429
461
|
end
|
430
462
|
|
431
463
|
def self.create_socket(session)
|
432
|
-
|
433
|
-
|
464
|
+
opts = {
|
465
|
+
:connect_timeout => session.connect_timeout * 1000,
|
466
|
+
# send_timeout is ignored in JRuby
|
467
|
+
:so_timeout => session.receive_timeout * 1000,
|
468
|
+
:tcp_keepalive => session.tcp_keepalive,
|
469
|
+
:debug_dev => session.debug_dev
|
470
|
+
}
|
471
|
+
socket = nil
|
434
472
|
begin
|
435
473
|
if session.proxy
|
474
|
+
site = session.proxy || session.dest
|
475
|
+
socket = JavaSocketWrap.connect(Socket.new, site, opts)
|
436
476
|
session.connect_ssl_proxy(JavaSocketWrap.new(socket), Util.urify(session.dest.to_s))
|
437
477
|
end
|
478
|
+
new(socket, session.dest, session.ssl_config, opts)
|
438
479
|
rescue
|
439
|
-
socket.close
|
480
|
+
socket.close if socket
|
440
481
|
raise
|
441
482
|
end
|
442
|
-
new(socket, session.dest, session.ssl_config, session.debug_dev)
|
443
483
|
end
|
444
484
|
|
445
|
-
DEFAULT_SSL_PROTOCOL = 'TLS'
|
446
|
-
def initialize(socket, dest, config,
|
485
|
+
DEFAULT_SSL_PROTOCOL = (java.lang.System.getProperty('java.specification.version') == '1.7') ? 'TLSv1.2' : 'TLS'
|
486
|
+
def initialize(socket, dest, config, opts = {})
|
487
|
+
@config = config
|
488
|
+
begin
|
489
|
+
@ssl_socket = create_ssl_socket(socket, dest, config, opts)
|
490
|
+
ssl_version = java_ssl_version(config)
|
491
|
+
@ssl_socket.setEnabledProtocols([ssl_version].to_java(java.lang.String)) if ssl_version != DEFAULT_SSL_PROTOCOL
|
492
|
+
if config.ciphers != SSLConfig::CIPHERS_DEFAULT
|
493
|
+
@ssl_socket.setEnabledCipherSuites(config.ciphers.to_java(java.lang.String))
|
494
|
+
end
|
495
|
+
ssl_connect(dest.host)
|
496
|
+
rescue java.security.GeneralSecurityException => e
|
497
|
+
raise OpenSSL::SSL::SSLError.new(e.getMessage)
|
498
|
+
rescue java.net.SocketTimeoutException => e
|
499
|
+
raise HTTPClient::ConnectTimeoutError.new(e.getMessage)
|
500
|
+
rescue java.io.IOException => e
|
501
|
+
raise OpenSSL::SSL::SSLError.new("#{e.class}: #{e.getMessage}")
|
502
|
+
end
|
503
|
+
|
504
|
+
super(@ssl_socket, opts[:debug_dev])
|
505
|
+
end
|
506
|
+
|
507
|
+
def java_ssl_version(config)
|
447
508
|
if config.ssl_version == :auto
|
448
|
-
|
509
|
+
DEFAULT_SSL_PROTOCOL
|
449
510
|
else
|
450
|
-
|
511
|
+
config.ssl_version.to_s.tr('_', '.')
|
451
512
|
end
|
513
|
+
end
|
514
|
+
|
515
|
+
def create_ssl_context(config)
|
452
516
|
unless config.cert_store_crl_items.empty?
|
453
517
|
raise NotImplementedError.new('Manual CRL configuration is not yet supported')
|
454
518
|
end
|
@@ -464,7 +528,7 @@ unless defined?(SSLSocket)
|
|
464
528
|
|
465
529
|
trust_store = nil
|
466
530
|
verify_callback = config.verify_callback || config.method(:default_verify_callback)
|
467
|
-
if config.
|
531
|
+
if !config.verify?
|
468
532
|
tmf = VerifyNoneTrustManagerFactory.new(verify_callback)
|
469
533
|
else
|
470
534
|
tmf = SystemTrustManagerFactory.new(verify_callback)
|
@@ -477,36 +541,24 @@ unless defined?(SSLSocket)
|
|
477
541
|
tmf.init(trust_store)
|
478
542
|
tm = tmf.getTrustManagers
|
479
543
|
|
480
|
-
ctx = SSLContext.getInstance(
|
544
|
+
ctx = SSLContext.getInstance(java_ssl_version(config))
|
481
545
|
ctx.init(km, tm, nil)
|
482
546
|
if config.timeout
|
483
547
|
ctx.getClientSessionContext.setSessionTimeout(config.timeout)
|
484
548
|
end
|
549
|
+
ctx
|
550
|
+
end
|
485
551
|
|
552
|
+
def create_ssl_socket(socket, dest, config, opts)
|
553
|
+
ctx = create_ssl_context(config)
|
486
554
|
factory = ctx.getSocketFactory
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
end
|
493
|
-
ssl_socket.startHandshake
|
494
|
-
ssl_session = ssl_socket.getSession
|
495
|
-
@peer_cert = JavaCertificate.new(ssl_session.getPeerCertificates.first)
|
496
|
-
if $DEBUG
|
497
|
-
warn("Protocol version: #{ssl_session.getProtocol}")
|
498
|
-
warn("Cipher: #{ssl_socket.getSession.getCipherSuite}")
|
499
|
-
end
|
500
|
-
post_connection_check(dest.host, @peer_cert)
|
501
|
-
rescue java.security.GeneralSecurityException => e
|
502
|
-
raise OpenSSL::SSL::SSLError.new(e.getMessage)
|
503
|
-
rescue javax.net.ssl.SSLException => e
|
504
|
-
raise OpenSSL::SSL::SSLError.new(e.getMessage)
|
505
|
-
rescue java.net.SocketException => e
|
506
|
-
raise OpenSSL::SSL::SSLError.new(e.getMessage)
|
555
|
+
unless socket
|
556
|
+
# Create a plain socket first to set connection timeouts on,
|
557
|
+
# then wrap it in a SSL socket so that SNI gets setup on it.
|
558
|
+
socket = javax.net.SocketFactory.getDefault.createSocket
|
559
|
+
JavaSocketWrap.connect(socket, dest, opts)
|
507
560
|
end
|
508
|
-
|
509
|
-
super(ssl_socket, debug_dev)
|
561
|
+
factory.createSocket(socket, dest.host, dest.port, true)
|
510
562
|
end
|
511
563
|
|
512
564
|
def peer_cert
|
@@ -515,8 +567,23 @@ unless defined?(SSLSocket)
|
|
515
567
|
|
516
568
|
private
|
517
569
|
|
518
|
-
def
|
519
|
-
|
570
|
+
def ssl_connect(hostname)
|
571
|
+
@ssl_socket.startHandshake
|
572
|
+
ssl_session = @ssl_socket.getSession
|
573
|
+
@peer_cert = JavaCertificate.new(ssl_session.getPeerCertificates.first)
|
574
|
+
if $DEBUG
|
575
|
+
warn("Protocol version: #{ssl_session.getProtocol}")
|
576
|
+
warn("Cipher: #{@ssl_socket.getSession.getCipherSuite}")
|
577
|
+
end
|
578
|
+
post_connection_check(hostname)
|
579
|
+
end
|
580
|
+
|
581
|
+
def post_connection_check(hostname)
|
582
|
+
if !@config.verify?
|
583
|
+
return
|
584
|
+
else
|
585
|
+
BrowserCompatHostnameVerifier.new.verify(hostname, @peer_cert.cert)
|
586
|
+
end
|
520
587
|
end
|
521
588
|
end
|
522
589
|
|
data/lib/httpclient/session.rb
CHANGED
@@ -21,7 +21,7 @@ require 'zlib'
|
|
21
21
|
require 'httpclient/timeout' # TODO: remove this once we drop 1.8 support
|
22
22
|
require 'httpclient/ssl_config'
|
23
23
|
require 'httpclient/http'
|
24
|
-
if defined?
|
24
|
+
if defined? JRUBY_VERSION
|
25
25
|
require 'httpclient/jruby_ssl_socket'
|
26
26
|
else
|
27
27
|
require 'httpclient/ssl_socket'
|
@@ -76,7 +76,7 @@ class HTTPClient
|
|
76
76
|
def to_s # :nodoc:
|
77
77
|
addr
|
78
78
|
end
|
79
|
-
|
79
|
+
|
80
80
|
# Returns true if scheme, host and port of the given URI matches with this.
|
81
81
|
def match(uri)
|
82
82
|
(@scheme == uri.scheme) and (@host == uri.host) and (@port == uri.port.to_i)
|
@@ -105,6 +105,8 @@ class HTTPClient
|
|
105
105
|
attr_accessor :debug_dev
|
106
106
|
# Boolean value for Socket#sync
|
107
107
|
attr_accessor :socket_sync
|
108
|
+
# Boolean value to send TCP keepalive packets; no timing settings exist at present
|
109
|
+
attr_accessor :tcp_keepalive
|
108
110
|
|
109
111
|
attr_accessor :connect_timeout
|
110
112
|
# Maximum retry count. 0 for infinite.
|
@@ -115,6 +117,9 @@ class HTTPClient
|
|
115
117
|
attr_accessor :read_block_size
|
116
118
|
attr_accessor :protocol_retry_count
|
117
119
|
|
120
|
+
# Raise BadResponseError if response size does not match with Content-Length header in response.
|
121
|
+
attr_accessor :strict_response_size_check
|
122
|
+
|
118
123
|
# Local address to bind local side of the socket to
|
119
124
|
attr_accessor :socket_local
|
120
125
|
|
@@ -134,6 +139,7 @@ class HTTPClient
|
|
134
139
|
@protocol_version = nil
|
135
140
|
@debug_dev = client.debug_dev
|
136
141
|
@socket_sync = true
|
142
|
+
@tcp_keepalive = false
|
137
143
|
@chunk_size = ::HTTP::Message::Body::DEFAULT_CHUNK_SIZE
|
138
144
|
|
139
145
|
@connect_timeout = 60
|
@@ -148,6 +154,7 @@ class HTTPClient
|
|
148
154
|
@test_loopback_http_response = []
|
149
155
|
|
150
156
|
@transparent_gzip_decompression = false
|
157
|
+
@strict_response_size_check = false
|
151
158
|
@socket_local = Site.new
|
152
159
|
|
153
160
|
@sess_pool = {}
|
@@ -212,6 +219,7 @@ class HTTPClient
|
|
212
219
|
sess = Session.new(@client, site, @agent_name, @from)
|
213
220
|
sess.proxy = via_proxy ? @proxy : nil
|
214
221
|
sess.socket_sync = @socket_sync
|
222
|
+
sess.tcp_keepalive = @tcp_keepalive
|
215
223
|
sess.requested_version = @protocol_version if @protocol_version
|
216
224
|
sess.connect_timeout = @connect_timeout
|
217
225
|
sess.connect_retry = @connect_retry
|
@@ -221,6 +229,7 @@ class HTTPClient
|
|
221
229
|
sess.protocol_retry_count = @protocol_retry_count
|
222
230
|
sess.ssl_config = @ssl_config
|
223
231
|
sess.debug_dev = @debug_dev
|
232
|
+
sess.strict_response_size_check = @strict_response_size_check
|
224
233
|
sess.socket_local = @socket_local
|
225
234
|
sess.test_loopback_http_response = @test_loopback_http_response
|
226
235
|
sess.transparent_gzip_decompression = @transparent_gzip_decompression
|
@@ -432,6 +441,8 @@ class HTTPClient
|
|
432
441
|
attr_accessor :proxy
|
433
442
|
# Boolean value for Socket#sync
|
434
443
|
attr_accessor :socket_sync
|
444
|
+
# Boolean value to send TCP keepalive packets; no timing settings exist at present
|
445
|
+
attr_accessor :tcp_keepalive
|
435
446
|
# Requested protocol version
|
436
447
|
attr_accessor :requested_version
|
437
448
|
# Device for dumping log for debugging
|
@@ -444,6 +455,7 @@ class HTTPClient
|
|
444
455
|
attr_accessor :read_block_size
|
445
456
|
attr_accessor :protocol_retry_count
|
446
457
|
|
458
|
+
attr_accessor :strict_response_size_check
|
447
459
|
attr_accessor :socket_local
|
448
460
|
|
449
461
|
attr_accessor :ssl_config
|
@@ -458,6 +470,7 @@ class HTTPClient
|
|
458
470
|
@dest = dest
|
459
471
|
@proxy = nil
|
460
472
|
@socket_sync = true
|
473
|
+
@tcp_keepalive = false
|
461
474
|
@requested_version = nil
|
462
475
|
|
463
476
|
@debug_dev = nil
|
@@ -473,6 +486,7 @@ class HTTPClient
|
|
473
486
|
@ssl_peer_cert = nil
|
474
487
|
|
475
488
|
@test_loopback_http_response = nil
|
489
|
+
@strict_response_size_check = false
|
476
490
|
@socket_local = Site::EMPTY
|
477
491
|
|
478
492
|
@agent_name = agent_name
|
@@ -599,17 +613,16 @@ class HTTPClient
|
|
599
613
|
clean_local = @socket_local.host.delete("[]")
|
600
614
|
socket = TCPSocket.new(clean_host, port, clean_local, @socket_local.port)
|
601
615
|
end
|
616
|
+
socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true) if @tcp_keepalive
|
602
617
|
if @debug_dev
|
603
618
|
@debug_dev << "! CONNECTION ESTABLISHED\n"
|
604
619
|
socket.extend(DebugSocket)
|
605
620
|
socket.debug_dev = @debug_dev
|
606
621
|
end
|
607
622
|
rescue SystemCallError => e
|
608
|
-
e.message
|
609
|
-
raise
|
623
|
+
raise e.class, e.message + " (#{host}:#{port})"
|
610
624
|
rescue SocketError => e
|
611
|
-
e.message
|
612
|
-
raise
|
625
|
+
raise e.class, e.message + " (#{host}:#{port})"
|
613
626
|
end
|
614
627
|
socket
|
615
628
|
end
|
@@ -871,6 +884,9 @@ class HTTPClient
|
|
871
884
|
rescue EOFError
|
872
885
|
close
|
873
886
|
buf = nil
|
887
|
+
if @strict_response_size_check
|
888
|
+
raise BadResponseError.new("EOF while reading rest #{@content_length} bytes")
|
889
|
+
end
|
874
890
|
end
|
875
891
|
end
|
876
892
|
if buf && buf.bytesize > 0
|
@@ -887,18 +903,18 @@ class HTTPClient
|
|
887
903
|
def read_body_chunked(&block)
|
888
904
|
buf = empty_bin_str
|
889
905
|
while true
|
890
|
-
len = @socket.gets(RS)
|
891
|
-
if len.nil? # EOF
|
892
|
-
close
|
893
|
-
return
|
894
|
-
end
|
895
|
-
@chunk_length = len.hex
|
896
|
-
if @chunk_length == 0
|
897
|
-
@content_length = 0
|
898
|
-
@socket.gets(RS)
|
899
|
-
return
|
900
|
-
end
|
901
906
|
::Timeout.timeout(@receive_timeout, ReceiveTimeoutError) do
|
907
|
+
len = @socket.gets(RS)
|
908
|
+
if len.nil? # EOF
|
909
|
+
close
|
910
|
+
return
|
911
|
+
end
|
912
|
+
@chunk_length = len.hex
|
913
|
+
if @chunk_length == 0
|
914
|
+
@content_length = 0
|
915
|
+
@socket.gets(RS)
|
916
|
+
return
|
917
|
+
end
|
902
918
|
@socket.read(@chunk_length, buf)
|
903
919
|
@socket.read(2)
|
904
920
|
end
|
@@ -920,6 +936,9 @@ class HTTPClient
|
|
920
936
|
@socket.readpartial(@read_block_size, buf)
|
921
937
|
rescue EOFError
|
922
938
|
buf = nil
|
939
|
+
if @strict_response_size_check
|
940
|
+
raise BadResponseError.new("EOF while reading chunked response")
|
941
|
+
end
|
923
942
|
end
|
924
943
|
end
|
925
944
|
if buf && buf.bytesize > 0
|
@@ -931,7 +950,7 @@ class HTTPClient
|
|
931
950
|
end
|
932
951
|
|
933
952
|
def empty_bin_str
|
934
|
-
str = ''
|
953
|
+
str = ''.dup
|
935
954
|
str.force_encoding('BINARY') if str.respond_to?(:force_encoding)
|
936
955
|
str
|
937
956
|
end
|