httpclient 2.6.0.1 → 2.8.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.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/bin/httpclient +7 -1
- data/lib/http-access2.rb +1 -1
- data/lib/httpclient/auth.rb +3 -3
- data/lib/httpclient/cacert.pem +3952 -0
- data/lib/httpclient/connection.rb +1 -1
- data/lib/httpclient/cookie.rb +10 -10
- data/lib/httpclient/http.rb +9 -4
- data/lib/httpclient/jruby_ssl_socket.rb +588 -0
- data/lib/httpclient/session.rb +199 -262
- data/lib/httpclient/ssl_config.rb +123 -114
- data/lib/httpclient/ssl_socket.rb +150 -0
- data/lib/httpclient/timeout.rb +1 -1
- data/lib/httpclient/util.rb +33 -1
- data/lib/httpclient/version.rb +1 -1
- data/lib/httpclient/webagent-cookie.rb +2 -2
- data/lib/httpclient.rb +72 -20
- data/lib/oauthclient.rb +2 -1
- data/test/helper.rb +7 -5
- data/test/jruby_ssl_socket/test_pemutils.rb +32 -0
- data/test/test_auth.rb +28 -9
- data/test/test_cookie.rb +2 -2
- data/test/test_http-access2.rb +2 -0
- data/test/test_httpclient.rb +143 -23
- data/test/test_ssl.rb +295 -17
- data/test/test_webagent-cookie.rb +2 -2
- metadata +14 -10
- /data/lib/httpclient/{cacert.p7s → cacert1024.pem} +0 -0
- /data/test/{ca-chain.cert → ca-chain.pem} +0 -0
data/lib/httpclient/session.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# HTTPClient - HTTP client library.
|
2
|
-
# Copyright (C) 2000-
|
2
|
+
# Copyright (C) 2000-2015 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
|
3
3
|
#
|
4
4
|
# This program is copyrighted free software by NAKAMURA, Hiroshi. You can
|
5
5
|
# redistribute it and/or modify it under the same terms of Ruby's license;
|
@@ -14,12 +14,18 @@
|
|
14
14
|
|
15
15
|
require 'socket'
|
16
16
|
require 'thread'
|
17
|
+
require 'timeout'
|
17
18
|
require 'stringio'
|
18
19
|
require 'zlib'
|
19
20
|
|
20
21
|
require 'httpclient/timeout' # TODO: remove this once we drop 1.8 support
|
21
22
|
require 'httpclient/ssl_config'
|
22
23
|
require 'httpclient/http'
|
24
|
+
if RUBY_ENGINE == 'jruby'
|
25
|
+
require 'httpclient/jruby_ssl_socket'
|
26
|
+
else
|
27
|
+
require 'httpclient/ssl_socket'
|
28
|
+
end
|
23
29
|
|
24
30
|
|
25
31
|
class HTTPClient
|
@@ -70,7 +76,7 @@ class HTTPClient
|
|
70
76
|
def to_s # :nodoc:
|
71
77
|
addr
|
72
78
|
end
|
73
|
-
|
79
|
+
|
74
80
|
# Returns true if scheme, host and port of the given URI matches with this.
|
75
81
|
def match(uri)
|
76
82
|
(@scheme == uri.scheme) and (@host == uri.host) and (@port == uri.port.to_i)
|
@@ -99,6 +105,8 @@ class HTTPClient
|
|
99
105
|
attr_accessor :debug_dev
|
100
106
|
# Boolean value for Socket#sync
|
101
107
|
attr_accessor :socket_sync
|
108
|
+
# Boolean value to send TCP keepalive packets; no timing settings exist at present
|
109
|
+
attr_accessor :tcp_keepalive
|
102
110
|
|
103
111
|
attr_accessor :connect_timeout
|
104
112
|
# Maximum retry count. 0 for infinite.
|
@@ -109,6 +117,9 @@ class HTTPClient
|
|
109
117
|
attr_accessor :read_block_size
|
110
118
|
attr_accessor :protocol_retry_count
|
111
119
|
|
120
|
+
# Raise BadResponseError if response size does not match with Content-Length header in response.
|
121
|
+
attr_accessor :strict_response_size_check
|
122
|
+
|
112
123
|
# Local address to bind local side of the socket to
|
113
124
|
attr_accessor :socket_local
|
114
125
|
|
@@ -128,6 +139,7 @@ class HTTPClient
|
|
128
139
|
@protocol_version = nil
|
129
140
|
@debug_dev = client.debug_dev
|
130
141
|
@socket_sync = true
|
142
|
+
@tcp_keepalive = false
|
131
143
|
@chunk_size = ::HTTP::Message::Body::DEFAULT_CHUNK_SIZE
|
132
144
|
|
133
145
|
@connect_timeout = 60
|
@@ -142,6 +154,7 @@ class HTTPClient
|
|
142
154
|
@test_loopback_http_response = []
|
143
155
|
|
144
156
|
@transparent_gzip_decompression = false
|
157
|
+
@strict_response_size_check = false
|
145
158
|
@socket_local = Site.new
|
146
159
|
|
147
160
|
@sess_pool = {}
|
@@ -206,6 +219,7 @@ class HTTPClient
|
|
206
219
|
sess = Session.new(@client, site, @agent_name, @from)
|
207
220
|
sess.proxy = via_proxy ? @proxy : nil
|
208
221
|
sess.socket_sync = @socket_sync
|
222
|
+
sess.tcp_keepalive = @tcp_keepalive
|
209
223
|
sess.requested_version = @protocol_version if @protocol_version
|
210
224
|
sess.connect_timeout = @connect_timeout
|
211
225
|
sess.connect_retry = @connect_retry
|
@@ -215,6 +229,7 @@ class HTTPClient
|
|
215
229
|
sess.protocol_retry_count = @protocol_retry_count
|
216
230
|
sess.ssl_config = @ssl_config
|
217
231
|
sess.debug_dev = @debug_dev
|
232
|
+
sess.strict_response_size_check = @strict_response_size_check
|
218
233
|
sess.socket_local = @socket_local
|
219
234
|
sess.test_loopback_http_response = @test_loopback_http_response
|
220
235
|
sess.transparent_gzip_decompression = @transparent_gzip_decompression
|
@@ -288,131 +303,6 @@ class HTTPClient
|
|
288
303
|
end
|
289
304
|
|
290
305
|
|
291
|
-
# Wraps up OpenSSL::SSL::SSLSocket and offers debugging features.
|
292
|
-
class SSLSocketWrap
|
293
|
-
def initialize(socket, context, debug_dev = nil)
|
294
|
-
unless SSLEnabled
|
295
|
-
raise ConfigurationError.new('Ruby/OpenSSL module is required')
|
296
|
-
end
|
297
|
-
@context = context
|
298
|
-
@socket = socket
|
299
|
-
@ssl_socket = create_openssl_socket(@socket)
|
300
|
-
@debug_dev = debug_dev
|
301
|
-
end
|
302
|
-
|
303
|
-
def ssl_connect(hostname = nil)
|
304
|
-
if hostname && @ssl_socket.respond_to?(:hostname=)
|
305
|
-
@ssl_socket.hostname = hostname
|
306
|
-
end
|
307
|
-
@ssl_socket.connect
|
308
|
-
end
|
309
|
-
|
310
|
-
def post_connection_check(host)
|
311
|
-
verify_mode = @context.verify_mode || OpenSSL::SSL::VERIFY_NONE
|
312
|
-
if verify_mode == OpenSSL::SSL::VERIFY_NONE
|
313
|
-
return
|
314
|
-
elsif @ssl_socket.peer_cert.nil? and
|
315
|
-
check_mask(verify_mode, OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT)
|
316
|
-
raise OpenSSL::SSL::SSLError.new('no peer cert')
|
317
|
-
end
|
318
|
-
hostname = host.host
|
319
|
-
if @ssl_socket.respond_to?(:post_connection_check) and RUBY_VERSION > "1.8.4"
|
320
|
-
@ssl_socket.post_connection_check(hostname)
|
321
|
-
else
|
322
|
-
@context.post_connection_check(@ssl_socket.peer_cert, hostname)
|
323
|
-
end
|
324
|
-
end
|
325
|
-
|
326
|
-
def ssl_version
|
327
|
-
@ssl_socket.ssl_version if @ssl_socket.respond_to?(:ssl_version)
|
328
|
-
end
|
329
|
-
|
330
|
-
def ssl_cipher
|
331
|
-
@ssl_socket.cipher
|
332
|
-
end
|
333
|
-
|
334
|
-
def ssl_state
|
335
|
-
@ssl_socket.state
|
336
|
-
end
|
337
|
-
|
338
|
-
def peer_cert
|
339
|
-
@ssl_socket.peer_cert
|
340
|
-
end
|
341
|
-
|
342
|
-
def close
|
343
|
-
@ssl_socket.close
|
344
|
-
@socket.close
|
345
|
-
end
|
346
|
-
|
347
|
-
def closed?
|
348
|
-
@socket.closed?
|
349
|
-
end
|
350
|
-
|
351
|
-
def eof?
|
352
|
-
@ssl_socket.eof?
|
353
|
-
end
|
354
|
-
|
355
|
-
def gets(*args)
|
356
|
-
str = @ssl_socket.gets(*args)
|
357
|
-
debug(str)
|
358
|
-
str
|
359
|
-
end
|
360
|
-
|
361
|
-
def read(*args)
|
362
|
-
str = @ssl_socket.read(*args)
|
363
|
-
debug(str)
|
364
|
-
str
|
365
|
-
end
|
366
|
-
|
367
|
-
def readpartial(*args)
|
368
|
-
str = @ssl_socket.readpartial(*args)
|
369
|
-
debug(str)
|
370
|
-
str
|
371
|
-
end
|
372
|
-
|
373
|
-
def <<(str)
|
374
|
-
rv = @ssl_socket.write(str)
|
375
|
-
debug(str)
|
376
|
-
rv
|
377
|
-
end
|
378
|
-
|
379
|
-
def flush
|
380
|
-
@ssl_socket.flush
|
381
|
-
end
|
382
|
-
|
383
|
-
def sync
|
384
|
-
@ssl_socket.sync
|
385
|
-
end
|
386
|
-
|
387
|
-
def sync=(sync)
|
388
|
-
@ssl_socket.sync = sync
|
389
|
-
end
|
390
|
-
|
391
|
-
private
|
392
|
-
|
393
|
-
def check_mask(value, mask)
|
394
|
-
value & mask == mask
|
395
|
-
end
|
396
|
-
|
397
|
-
def create_openssl_socket(socket)
|
398
|
-
ssl_socket = nil
|
399
|
-
if OpenSSL::SSL.const_defined?("SSLContext")
|
400
|
-
ctx = OpenSSL::SSL::SSLContext.new
|
401
|
-
@context.set_context(ctx)
|
402
|
-
ssl_socket = OpenSSL::SSL::SSLSocket.new(socket, ctx)
|
403
|
-
else
|
404
|
-
ssl_socket = OpenSSL::SSL::SSLSocket.new(socket)
|
405
|
-
@context.set_context(ssl_socket)
|
406
|
-
end
|
407
|
-
ssl_socket
|
408
|
-
end
|
409
|
-
|
410
|
-
def debug(str)
|
411
|
-
@debug_dev << str if @debug_dev && str
|
412
|
-
end
|
413
|
-
end
|
414
|
-
|
415
|
-
|
416
306
|
# Wraps up a Socket for method interception.
|
417
307
|
module SocketWrap
|
418
308
|
def initialize(socket, *args)
|
@@ -432,20 +322,20 @@ class HTTPClient
|
|
432
322
|
@socket.eof?
|
433
323
|
end
|
434
324
|
|
435
|
-
def gets(
|
436
|
-
@socket.gets(
|
325
|
+
def gets(rs)
|
326
|
+
@socket.gets(rs)
|
437
327
|
end
|
438
328
|
|
439
|
-
def read(
|
440
|
-
@socket.read(
|
329
|
+
def read(size, buf = nil)
|
330
|
+
@socket.read(size, buf)
|
441
331
|
end
|
442
332
|
|
443
|
-
def readpartial(
|
333
|
+
def readpartial(size, buf = nil)
|
444
334
|
# StringIO doesn't support :readpartial
|
445
335
|
if @socket.respond_to?(:readpartial)
|
446
|
-
@socket.readpartial(
|
336
|
+
@socket.readpartial(size, buf)
|
447
337
|
else
|
448
|
-
@socket.read(
|
338
|
+
@socket.read(size, buf)
|
449
339
|
end
|
450
340
|
end
|
451
341
|
|
@@ -481,19 +371,19 @@ class HTTPClient
|
|
481
371
|
debug("! CONNECTION CLOSED\n")
|
482
372
|
end
|
483
373
|
|
484
|
-
def gets(
|
374
|
+
def gets(rs)
|
485
375
|
str = super
|
486
376
|
debug(str)
|
487
377
|
str
|
488
378
|
end
|
489
379
|
|
490
|
-
def read(
|
380
|
+
def read(size, buf = nil)
|
491
381
|
str = super
|
492
382
|
debug(str)
|
493
383
|
str
|
494
384
|
end
|
495
385
|
|
496
|
-
def readpartial(
|
386
|
+
def readpartial(size, buf = nil)
|
497
387
|
str = super
|
498
388
|
debug(str)
|
499
389
|
str
|
@@ -533,6 +423,10 @@ class HTTPClient
|
|
533
423
|
def <<(str)
|
534
424
|
# ignored
|
535
425
|
end
|
426
|
+
|
427
|
+
def peer_cert
|
428
|
+
nil
|
429
|
+
end
|
536
430
|
end
|
537
431
|
|
538
432
|
|
@@ -547,6 +441,8 @@ class HTTPClient
|
|
547
441
|
attr_accessor :proxy
|
548
442
|
# Boolean value for Socket#sync
|
549
443
|
attr_accessor :socket_sync
|
444
|
+
# Boolean value to send TCP keepalive packets; no timing settings exist at present
|
445
|
+
attr_accessor :tcp_keepalive
|
550
446
|
# Requested protocol version
|
551
447
|
attr_accessor :requested_version
|
552
448
|
# Device for dumping log for debugging
|
@@ -559,6 +455,7 @@ class HTTPClient
|
|
559
455
|
attr_accessor :read_block_size
|
560
456
|
attr_accessor :protocol_retry_count
|
561
457
|
|
458
|
+
attr_accessor :strict_response_size_check
|
562
459
|
attr_accessor :socket_local
|
563
460
|
|
564
461
|
attr_accessor :ssl_config
|
@@ -573,6 +470,7 @@ class HTTPClient
|
|
573
470
|
@dest = dest
|
574
471
|
@proxy = nil
|
575
472
|
@socket_sync = true
|
473
|
+
@tcp_keepalive = false
|
576
474
|
@requested_version = nil
|
577
475
|
|
578
476
|
@debug_dev = nil
|
@@ -588,6 +486,7 @@ class HTTPClient
|
|
588
486
|
@ssl_peer_cert = nil
|
589
487
|
|
590
488
|
@test_loopback_http_response = nil
|
489
|
+
@strict_response_size_check = false
|
591
490
|
@socket_local = Site::EMPTY
|
592
491
|
|
593
492
|
@agent_name = agent_name
|
@@ -613,7 +512,7 @@ class HTTPClient
|
|
613
512
|
# Use absolute URI (not absolute path) iif via proxy AND not HTTPS.
|
614
513
|
req.header.request_absolute_uri = !@proxy.nil? && !https?(@dest)
|
615
514
|
begin
|
616
|
-
timeout(@send_timeout, SendTimeoutError) do
|
515
|
+
::Timeout.timeout(@send_timeout, SendTimeoutError) do
|
617
516
|
set_header(req)
|
618
517
|
req.dump(@socket)
|
619
518
|
# flush the IO stream as IO::sync mode is false
|
@@ -679,18 +578,8 @@ class HTTPClient
|
|
679
578
|
begin
|
680
579
|
read_header if @state == :META
|
681
580
|
return nil if @state != :DATA
|
682
|
-
if @
|
683
|
-
|
684
|
-
# - zlib 1.2.5 Manual
|
685
|
-
# http://www.zlib.net/manual.html#Advanced
|
686
|
-
# > windowBits can also be greater than 15 for optional gzip decoding. Add 32 to
|
687
|
-
# > windowBits to enable zlib and gzip decoding with automatic header detection,
|
688
|
-
# > or add 16 to decode only the gzip format
|
689
|
-
inflate_stream = Zlib::Inflate.new(Zlib::MAX_WBITS + 32)
|
690
|
-
original_block = block
|
691
|
-
block = Proc.new { |buf|
|
692
|
-
original_block.call(inflate_stream.inflate(buf))
|
693
|
-
}
|
581
|
+
if @transparent_gzip_decompression
|
582
|
+
block = content_inflater_block(@content_encoding, block)
|
694
583
|
end
|
695
584
|
if @chunked
|
696
585
|
read_body_chunked(&block)
|
@@ -713,8 +602,121 @@ class HTTPClient
|
|
713
602
|
nil
|
714
603
|
end
|
715
604
|
|
605
|
+
def create_socket(host, port)
|
606
|
+
socket = nil
|
607
|
+
begin
|
608
|
+
@debug_dev << "! CONNECT TO #{host}:#{port}\n" if @debug_dev
|
609
|
+
clean_host = host.delete("[]")
|
610
|
+
if @socket_local == Site::EMPTY
|
611
|
+
socket = TCPSocket.new(clean_host, port)
|
612
|
+
else
|
613
|
+
clean_local = @socket_local.host.delete("[]")
|
614
|
+
socket = TCPSocket.new(clean_host, port, clean_local, @socket_local.port)
|
615
|
+
end
|
616
|
+
socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true) if @tcp_keepalive
|
617
|
+
if @debug_dev
|
618
|
+
@debug_dev << "! CONNECTION ESTABLISHED\n"
|
619
|
+
socket.extend(DebugSocket)
|
620
|
+
socket.debug_dev = @debug_dev
|
621
|
+
end
|
622
|
+
rescue SystemCallError => e
|
623
|
+
raise e.class, e.message + " (#{host}:#{port})"
|
624
|
+
rescue SocketError => e
|
625
|
+
raise e.class, e.message + " (#{host}:#{port})"
|
626
|
+
end
|
627
|
+
socket
|
628
|
+
end
|
629
|
+
|
630
|
+
def create_loopback_socket(host, port, str)
|
631
|
+
@debug_dev << "! CONNECT TO #{host}:#{port}\n" if @debug_dev
|
632
|
+
socket = LoopBackSocket.new(host, port, str)
|
633
|
+
if @debug_dev
|
634
|
+
@debug_dev << "! CONNECTION ESTABLISHED\n"
|
635
|
+
socket.extend(DebugSocket)
|
636
|
+
socket.debug_dev = @debug_dev
|
637
|
+
end
|
638
|
+
if https?(@dest) && @proxy
|
639
|
+
connect_ssl_proxy(socket, Util.urify(@dest.to_s))
|
640
|
+
end
|
641
|
+
socket
|
642
|
+
end
|
643
|
+
|
644
|
+
def connect_ssl_proxy(socket, uri)
|
645
|
+
req = HTTP::Message.new_connect_request(uri)
|
646
|
+
@client.request_filter.each do |filter|
|
647
|
+
filter.filter_request(req)
|
648
|
+
end
|
649
|
+
set_header(req)
|
650
|
+
req.dump(socket)
|
651
|
+
socket.flush unless @socket_sync
|
652
|
+
res = HTTP::Message.new_response('')
|
653
|
+
parse_header(socket)
|
654
|
+
res.http_version, res.status, res.reason = @version, @status, @reason
|
655
|
+
@headers.each do |key, value|
|
656
|
+
res.header.set(key.to_s, value)
|
657
|
+
end
|
658
|
+
commands = @client.request_filter.collect { |filter|
|
659
|
+
filter.filter_response(req, res)
|
660
|
+
}
|
661
|
+
if commands.find { |command| command == :retry }
|
662
|
+
raise RetryableResponse.new(res)
|
663
|
+
end
|
664
|
+
unless @status == 200
|
665
|
+
raise BadResponseError.new("connect to ssl proxy failed with status #{@status} #{@reason}", res)
|
666
|
+
end
|
667
|
+
end
|
668
|
+
|
716
669
|
private
|
717
670
|
|
671
|
+
# This inflater allows deflate compression with/without zlib header
|
672
|
+
class LenientInflater
|
673
|
+
def initialize
|
674
|
+
@inflater = Zlib::Inflate.new(Zlib::MAX_WBITS)
|
675
|
+
@first = true
|
676
|
+
end
|
677
|
+
|
678
|
+
def inflate(body)
|
679
|
+
if @first
|
680
|
+
first_inflate(body)
|
681
|
+
else
|
682
|
+
@inflater.inflate(body)
|
683
|
+
end
|
684
|
+
end
|
685
|
+
|
686
|
+
private
|
687
|
+
|
688
|
+
def first_inflate(body)
|
689
|
+
@first = false
|
690
|
+
begin
|
691
|
+
@inflater.inflate(body)
|
692
|
+
rescue Zlib::DataError
|
693
|
+
# fallback to deflate without zlib header
|
694
|
+
@inflater = Zlib::Inflate.new(-Zlib::MAX_WBITS)
|
695
|
+
@inflater.inflate(body)
|
696
|
+
end
|
697
|
+
end
|
698
|
+
end
|
699
|
+
|
700
|
+
def content_inflater_block(content_encoding, block)
|
701
|
+
case content_encoding
|
702
|
+
when 'gzip', 'x-gzip'
|
703
|
+
# zlib itself has a functionality to decompress gzip stream.
|
704
|
+
# - zlib 1.2.5 Manual
|
705
|
+
# http://www.zlib.net/manual.html#Advanced
|
706
|
+
# > windowBits can also be greater than 15 for optional gzip decoding. Add 32 to
|
707
|
+
# > windowBits to enable zlib and gzip decoding with automatic header detection,
|
708
|
+
# > or add 16 to decode only the gzip format
|
709
|
+
inflate_stream = Zlib::Inflate.new(Zlib::MAX_WBITS + 32)
|
710
|
+
when 'deflate'
|
711
|
+
inflate_stream = LenientInflater.new
|
712
|
+
else
|
713
|
+
return block
|
714
|
+
end
|
715
|
+
Proc.new { |buf|
|
716
|
+
block.call(inflate_stream.inflate(buf))
|
717
|
+
}
|
718
|
+
end
|
719
|
+
|
718
720
|
def set_header(req)
|
719
721
|
if @requested_version
|
720
722
|
if /^(?:HTTP\/|)(\d+.\d+)$/ =~ @requested_version
|
@@ -743,30 +745,15 @@ class HTTPClient
|
|
743
745
|
site = @proxy || @dest
|
744
746
|
retry_number = 0
|
745
747
|
begin
|
746
|
-
timeout(@connect_timeout, ConnectTimeoutError) do
|
747
|
-
|
748
|
-
|
749
|
-
|
750
|
-
|
751
|
-
|
752
|
-
|
753
|
-
|
754
|
-
begin
|
755
|
-
@socket.ssl_connect(@dest.host)
|
756
|
-
ensure
|
757
|
-
if $DEBUG
|
758
|
-
warn("Protocol version: #{@socket.ssl_version}")
|
759
|
-
warn("Cipher: #{@socket.ssl_cipher.inspect}")
|
760
|
-
warn("State: #{@socket.ssl_state}")
|
761
|
-
end
|
762
|
-
end
|
763
|
-
@socket.post_connection_check(@dest)
|
764
|
-
@ssl_peer_cert = @socket.peer_cert
|
765
|
-
end
|
748
|
+
::Timeout.timeout(@connect_timeout, ConnectTimeoutError) do
|
749
|
+
if str = @test_loopback_http_response.shift
|
750
|
+
@socket = create_loopback_socket(site.host, site.port, str)
|
751
|
+
elsif https?(@dest)
|
752
|
+
@socket = SSLSocket.create_socket(self)
|
753
|
+
@ssl_peer_cert = @socket.peer_cert
|
754
|
+
else
|
755
|
+
@socket = create_socket(site.host, site.port)
|
766
756
|
end
|
767
|
-
# Use Ruby internal buffering instead of passing data immediately
|
768
|
-
# to the underlying layer
|
769
|
-
# => we need to to call explicitly flush on the socket
|
770
757
|
@socket.sync = @socket_sync
|
771
758
|
end
|
772
759
|
rescue RetryableResponse
|
@@ -788,71 +775,13 @@ class HTTPClient
|
|
788
775
|
@state = :WAIT
|
789
776
|
end
|
790
777
|
|
791
|
-
def create_socket(site)
|
792
|
-
socket = nil
|
793
|
-
begin
|
794
|
-
@debug_dev << "! CONNECT TO #{site.host}:#{site.port}\n" if @debug_dev
|
795
|
-
clean_host = site.host.delete("[]")
|
796
|
-
if str = @test_loopback_http_response.shift
|
797
|
-
socket = LoopBackSocket.new(clean_host, site.port, str)
|
798
|
-
elsif @socket_local == Site::EMPTY
|
799
|
-
socket = TCPSocket.new(clean_host, site.port)
|
800
|
-
else
|
801
|
-
clean_local = @socket_local.host.delete("[]")
|
802
|
-
socket = TCPSocket.new(clean_host, site.port, clean_local, @socket_local.port)
|
803
|
-
end
|
804
|
-
if @debug_dev
|
805
|
-
@debug_dev << "! CONNECTION ESTABLISHED\n"
|
806
|
-
socket.extend(DebugSocket)
|
807
|
-
socket.debug_dev = @debug_dev
|
808
|
-
end
|
809
|
-
rescue SystemCallError => e
|
810
|
-
e.message << " (#{site})"
|
811
|
-
raise
|
812
|
-
rescue SocketError => e
|
813
|
-
e.message << " (#{site})"
|
814
|
-
raise
|
815
|
-
end
|
816
|
-
socket
|
817
|
-
end
|
818
|
-
|
819
|
-
# wrap socket with OpenSSL.
|
820
|
-
def create_ssl_socket(raw_socket)
|
821
|
-
SSLSocketWrap.new(raw_socket, @ssl_config, @debug_dev)
|
822
|
-
end
|
823
|
-
|
824
|
-
def connect_ssl_proxy(socket, uri)
|
825
|
-
req = HTTP::Message.new_connect_request(uri)
|
826
|
-
@client.request_filter.each do |filter|
|
827
|
-
filter.filter_request(req)
|
828
|
-
end
|
829
|
-
set_header(req)
|
830
|
-
req.dump(@socket)
|
831
|
-
@socket.flush unless @socket_sync
|
832
|
-
res = HTTP::Message.new_response('')
|
833
|
-
parse_header
|
834
|
-
res.http_version, res.status, res.reason = @version, @status, @reason
|
835
|
-
@headers.each do |key, value|
|
836
|
-
res.header.set(key.to_s, value)
|
837
|
-
end
|
838
|
-
commands = @client.request_filter.collect { |filter|
|
839
|
-
filter.filter_response(req, res)
|
840
|
-
}
|
841
|
-
if commands.find { |command| command == :retry }
|
842
|
-
raise RetryableResponse.new(res)
|
843
|
-
end
|
844
|
-
unless @status == 200
|
845
|
-
raise BadResponseError.new("connect to ssl proxy failed with status #{@status} #{@reason}", res)
|
846
|
-
end
|
847
|
-
end
|
848
|
-
|
849
778
|
# Read status block.
|
850
779
|
def read_header
|
851
780
|
@content_length = nil
|
852
781
|
@chunked = false
|
853
|
-
@
|
782
|
+
@content_encoding = nil
|
854
783
|
@chunk_length = 0
|
855
|
-
parse_header
|
784
|
+
parse_header(@socket)
|
856
785
|
# Header of the request has been parsed.
|
857
786
|
@state = :DATA
|
858
787
|
req = @requests.shift
|
@@ -868,12 +797,12 @@ class HTTPClient
|
|
868
797
|
end
|
869
798
|
|
870
799
|
StatusParseRegexp = %r(\AHTTP/(\d+\.\d+)\s+(\d\d\d)\s*([^\r\n]+)?\r?\n\z)
|
871
|
-
def parse_header
|
872
|
-
timeout(@receive_timeout, ReceiveTimeoutError) do
|
800
|
+
def parse_header(socket)
|
801
|
+
::Timeout.timeout(@receive_timeout, ReceiveTimeoutError) do
|
873
802
|
initial_line = nil
|
874
803
|
begin
|
875
804
|
begin
|
876
|
-
initial_line =
|
805
|
+
initial_line = socket.gets("\n")
|
877
806
|
if initial_line.nil?
|
878
807
|
close
|
879
808
|
raise KeepAliveDisconnected.new(self)
|
@@ -896,7 +825,7 @@ class HTTPClient
|
|
896
825
|
@next_connection = HTTP::Message.keep_alive_enabled?(@version)
|
897
826
|
@headers = []
|
898
827
|
while true
|
899
|
-
line =
|
828
|
+
line = socket.gets("\n")
|
900
829
|
unless line
|
901
830
|
raise BadResponseError.new('unexpected EOF')
|
902
831
|
end
|
@@ -908,7 +837,7 @@ class HTTPClient
|
|
908
837
|
last << line.strip
|
909
838
|
else
|
910
839
|
key, value = line.strip.split(/\s*:\s*/, 2)
|
911
|
-
|
840
|
+
parse_content_header(key, value)
|
912
841
|
@headers << [key, value]
|
913
842
|
end
|
914
843
|
end
|
@@ -921,18 +850,20 @@ class HTTPClient
|
|
921
850
|
((status >= 100 && status < 200) || status == 204 || status == 304)
|
922
851
|
end
|
923
852
|
|
924
|
-
def
|
853
|
+
def parse_content_header(key, value)
|
925
854
|
key = key.downcase
|
926
|
-
|
855
|
+
case key
|
856
|
+
when 'content-length'
|
927
857
|
@content_length = value.to_i
|
928
|
-
|
929
|
-
|
930
|
-
|
931
|
-
|
932
|
-
|
933
|
-
|
934
|
-
|
935
|
-
|
858
|
+
when 'content-encoding'
|
859
|
+
@content_encoding = value.downcase
|
860
|
+
when 'transfer-encoding'
|
861
|
+
if value.downcase == 'chunked'
|
862
|
+
@chunked = true
|
863
|
+
@chunk_length = 0
|
864
|
+
@content_length = nil
|
865
|
+
end
|
866
|
+
when 'connection', 'proxy-connection'
|
936
867
|
if value.downcase == 'keep-alive'
|
937
868
|
@next_connection = true
|
938
869
|
else
|
@@ -947,12 +878,15 @@ class HTTPClient
|
|
947
878
|
buf = empty_bin_str
|
948
879
|
maxbytes = @read_block_size
|
949
880
|
maxbytes = @content_length if maxbytes > @content_length && @content_length > 0
|
950
|
-
timeout(@receive_timeout, ReceiveTimeoutError) do
|
881
|
+
::Timeout.timeout(@receive_timeout, ReceiveTimeoutError) do
|
951
882
|
begin
|
952
883
|
@socket.readpartial(maxbytes, buf)
|
953
884
|
rescue EOFError
|
954
885
|
close
|
955
886
|
buf = nil
|
887
|
+
if @strict_response_size_check
|
888
|
+
raise BadResponseError.new("EOF while reading rest #{@content_length} bytes")
|
889
|
+
end
|
956
890
|
end
|
957
891
|
end
|
958
892
|
if buf && buf.bytesize > 0
|
@@ -969,18 +903,18 @@ class HTTPClient
|
|
969
903
|
def read_body_chunked(&block)
|
970
904
|
buf = empty_bin_str
|
971
905
|
while true
|
972
|
-
|
973
|
-
|
974
|
-
|
975
|
-
|
976
|
-
|
977
|
-
|
978
|
-
|
979
|
-
@
|
980
|
-
|
981
|
-
|
982
|
-
|
983
|
-
|
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
|
984
918
|
@socket.read(@chunk_length, buf)
|
985
919
|
@socket.read(2)
|
986
920
|
end
|
@@ -997,11 +931,14 @@ class HTTPClient
|
|
997
931
|
end
|
998
932
|
while true
|
999
933
|
buf = empty_bin_str
|
1000
|
-
timeout(@receive_timeout, ReceiveTimeoutError) do
|
934
|
+
::Timeout.timeout(@receive_timeout, ReceiveTimeoutError) do
|
1001
935
|
begin
|
1002
936
|
@socket.readpartial(@read_block_size, buf)
|
1003
937
|
rescue EOFError
|
1004
938
|
buf = nil
|
939
|
+
if @strict_response_size_check
|
940
|
+
raise BadResponseError.new("EOF while reading chunked response")
|
941
|
+
end
|
1005
942
|
end
|
1006
943
|
end
|
1007
944
|
if buf && buf.bytesize > 0
|