httpclient 2.1.5.2 → 2.1.6
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/hexdump.rb +36 -0
- data/lib/httpclient.rb +83 -43
- data/lib/httpclient/auth.rb +260 -15
- data/lib/httpclient/cacert.p7s +545 -266
- data/lib/httpclient/cacert_sha1.p7s +552 -273
- data/lib/httpclient/connection.rb +5 -1
- data/lib/httpclient/cookie.rb +1 -2
- data/lib/httpclient/http.rb +106 -51
- data/lib/httpclient/session.rb +101 -21
- data/lib/httpclient/ssl_config.rb +3 -3
- data/lib/httpclient/timeout.rb +11 -9
- data/lib/httpclient/util.rb +17 -0
- data/lib/oauthclient.rb +110 -0
- metadata +27 -17
- data/lib/tags +0 -908
@@ -64,7 +64,11 @@ class HTTPClient
|
|
64
64
|
# Retrieves a HTTP::Message instance of HTTP response. Do not invoke this
|
65
65
|
# method twice for now. The second invocation will be blocked.
|
66
66
|
def pop
|
67
|
-
@queue.pop
|
67
|
+
response_or_exception = @queue.pop
|
68
|
+
if response_or_exception.is_a? Exception
|
69
|
+
raise response_or_exception
|
70
|
+
end
|
71
|
+
response_or_exception
|
68
72
|
end
|
69
73
|
|
70
74
|
def push(result) # :nodoc:
|
data/lib/httpclient/cookie.rb
CHANGED
@@ -337,7 +337,6 @@ class WebAgent
|
|
337
337
|
|
338
338
|
domainname = url.host
|
339
339
|
domain_orig, path_orig = domain, path
|
340
|
-
use_security = override
|
341
340
|
|
342
341
|
if domain
|
343
342
|
|
@@ -372,7 +371,7 @@ class WebAgent
|
|
372
371
|
# and IE does not check, too.
|
373
372
|
end
|
374
373
|
|
375
|
-
path ||= url.path.sub(%r|/[^/]
|
374
|
+
path ||= url.path.sub(%r|/[^/]*\z|, '')
|
376
375
|
domain ||= domainname
|
377
376
|
@cookies.synchronize do
|
378
377
|
cookie = find_cookie_info(domain, path, name)
|
data/lib/httpclient/http.rb
CHANGED
@@ -95,7 +95,7 @@ module HTTP
|
|
95
95
|
|
96
96
|
# Represents HTTP message header.
|
97
97
|
class Headers
|
98
|
-
# HTTP version in a HTTP header.
|
98
|
+
# HTTP version in a HTTP header. String.
|
99
99
|
attr_accessor :http_version
|
100
100
|
# Size of body. nil when size is unknown (e.g. chunked response).
|
101
101
|
attr_reader :body_size
|
@@ -109,7 +109,7 @@ module HTTP
|
|
109
109
|
# Request only. Requested query.
|
110
110
|
attr_accessor :request_query
|
111
111
|
# Request only. Requested via proxy or not.
|
112
|
-
attr_accessor :
|
112
|
+
attr_accessor :request_absolute_uri
|
113
113
|
|
114
114
|
# Response only. HTTP status
|
115
115
|
attr_reader :status_code
|
@@ -151,14 +151,14 @@ module HTTP
|
|
151
151
|
# Creates a Message::Headers. Use init_request, init_response, or
|
152
152
|
# init_connect_request for acutual initialize.
|
153
153
|
def initialize
|
154
|
-
@http_version = 1.1
|
154
|
+
@http_version = '1.1'
|
155
155
|
@body_size = nil
|
156
156
|
@chunked = false
|
157
157
|
|
158
158
|
@request_method = nil
|
159
159
|
@request_uri = nil
|
160
160
|
@request_query = nil
|
161
|
-
@
|
161
|
+
@request_absolute_uri = nil
|
162
162
|
|
163
163
|
@status_code = nil
|
164
164
|
@reason_phrase = nil
|
@@ -178,7 +178,7 @@ module HTTP
|
|
178
178
|
@request_method = 'CONNECT'
|
179
179
|
@request_uri = uri
|
180
180
|
@request_query = nil
|
181
|
-
@http_version = 1.0
|
181
|
+
@http_version = '1.0'
|
182
182
|
end
|
183
183
|
|
184
184
|
# Placeholder URI object for nil uri.
|
@@ -189,7 +189,7 @@ module HTTP
|
|
189
189
|
@request_method = method
|
190
190
|
@request_uri = uri || NIL_URI
|
191
191
|
@request_query = query
|
192
|
-
@
|
192
|
+
@request_absolute_uri = false
|
193
193
|
end
|
194
194
|
|
195
195
|
# Initialize this instance as a response.
|
@@ -285,11 +285,38 @@ module HTTP
|
|
285
285
|
get(key).collect { |item| item[1] }
|
286
286
|
end
|
287
287
|
|
288
|
+
def create_query_uri()
|
289
|
+
if @request_method == 'CONNECT'
|
290
|
+
return "#{@request_uri.host}:#{@request_uri.port}"
|
291
|
+
end
|
292
|
+
path = @request_uri.path
|
293
|
+
path = '/' if path.nil? or path.empty?
|
294
|
+
if query_str = create_query_part()
|
295
|
+
path += "?#{query_str}"
|
296
|
+
end
|
297
|
+
path
|
298
|
+
end
|
299
|
+
|
300
|
+
def create_query_part()
|
301
|
+
query_str = nil
|
302
|
+
if @request_uri.query
|
303
|
+
query_str = @request_uri.query
|
304
|
+
end
|
305
|
+
if @request_query
|
306
|
+
if query_str
|
307
|
+
query_str += "&#{Message.create_query_part_str(@request_query)}"
|
308
|
+
else
|
309
|
+
query_str = Message.create_query_part_str(@request_query)
|
310
|
+
end
|
311
|
+
end
|
312
|
+
query_str
|
313
|
+
end
|
314
|
+
|
288
315
|
private
|
289
316
|
|
290
317
|
def request_line
|
291
|
-
path = create_query_uri(
|
292
|
-
if @
|
318
|
+
path = create_query_uri()
|
319
|
+
if @request_absolute_uri
|
293
320
|
path = "#{ @request_uri.scheme }://#{ @request_uri.host }:#{ @request_uri.port }#{ path }"
|
294
321
|
end
|
295
322
|
"#{ @request_method } #{ path } HTTP/#{ @http_version }#{ CRLF }"
|
@@ -323,7 +350,7 @@ module HTTP
|
|
323
350
|
elsif @body_size and (keep_alive or @body_size != 0)
|
324
351
|
set('Content-Length', @body_size.to_s)
|
325
352
|
end
|
326
|
-
if @http_version >= 1.1
|
353
|
+
if @http_version >= '1.1' and get('Host').empty?
|
327
354
|
if @request_uri.port == @request_uri.default_port
|
328
355
|
# GFE/1.3 dislikes default port number (returns 404)
|
329
356
|
set('Host', "#{@request_uri.host}")
|
@@ -358,29 +385,6 @@ module HTTP
|
|
358
385
|
def charset_label(charset)
|
359
386
|
CHARSET_MAP[charset] || 'us-ascii'
|
360
387
|
end
|
361
|
-
|
362
|
-
def create_query_uri(uri, query)
|
363
|
-
if @request_method == 'CONNECT'
|
364
|
-
return "#{uri.host}:#{uri.port}"
|
365
|
-
end
|
366
|
-
path = uri.path
|
367
|
-
path = '/' if path.nil? or path.empty?
|
368
|
-
query_str = nil
|
369
|
-
if uri.query
|
370
|
-
query_str = uri.query
|
371
|
-
end
|
372
|
-
if query
|
373
|
-
if query_str
|
374
|
-
query_str += "&#{Message.create_query_part_str(query)}"
|
375
|
-
else
|
376
|
-
query_str = Message.create_query_part_str(query)
|
377
|
-
end
|
378
|
-
end
|
379
|
-
if query_str
|
380
|
-
path += "?#{query_str}"
|
381
|
-
end
|
382
|
-
path
|
383
|
-
end
|
384
388
|
end
|
385
389
|
|
386
390
|
|
@@ -414,7 +418,9 @@ module HTTP
|
|
414
418
|
# Initialize this instance as a response.
|
415
419
|
def init_response(body = nil)
|
416
420
|
@body = body
|
417
|
-
if @body.respond_to?(:
|
421
|
+
if @body.respond_to?(:bytesize)
|
422
|
+
@size = @body.bytesize
|
423
|
+
elsif @body.respond_to?(:size)
|
418
424
|
@size = @body.size
|
419
425
|
else
|
420
426
|
@size = nil
|
@@ -438,6 +444,7 @@ module HTTP
|
|
438
444
|
while !part.read(@chunk_size, buf).nil?
|
439
445
|
dev << buf
|
440
446
|
end
|
447
|
+
part.rewind
|
441
448
|
else
|
442
449
|
dev << part
|
443
450
|
end
|
@@ -496,7 +503,7 @@ module HTTP
|
|
496
503
|
@size = @body.size
|
497
504
|
else
|
498
505
|
@body = Message.create_query_part_str(body)
|
499
|
-
@size = @body.
|
506
|
+
@size = @body.bytesize
|
500
507
|
end
|
501
508
|
end
|
502
509
|
|
@@ -517,7 +524,7 @@ module HTTP
|
|
517
524
|
end
|
518
525
|
|
519
526
|
def dump_chunk(str)
|
520
|
-
dump_chunk_size(str.
|
527
|
+
dump_chunk_size(str.bytesize) + (str + CRLF)
|
521
528
|
end
|
522
529
|
|
523
530
|
def dump_last_chunk
|
@@ -555,10 +562,10 @@ module HTTP
|
|
555
562
|
end
|
556
563
|
elsif @body[-1].is_a?(String)
|
557
564
|
@body[-1] += part.to_s
|
558
|
-
@size += part.to_s.
|
565
|
+
@size += part.to_s.bytesize if @size
|
559
566
|
else
|
560
567
|
@body << part.to_s
|
561
|
-
@size += part.to_s.
|
568
|
+
@size += part.to_s.bytesize if @size
|
562
569
|
end
|
563
570
|
end
|
564
571
|
|
@@ -583,11 +590,21 @@ module HTTP
|
|
583
590
|
}.join("; ")
|
584
591
|
if value.respond_to?(:mime_type)
|
585
592
|
content_type = value.mime_type
|
593
|
+
elsif value.respond_to?(:content_type)
|
594
|
+
content_type = value.content_type
|
586
595
|
else
|
587
|
-
|
596
|
+
path = value.respond_to?(:path) ? value.path : nil
|
597
|
+
content_type = Message.mime_type(path)
|
588
598
|
end
|
589
599
|
headers << %{Content-Disposition: form-data; name="#{attr}"; #{param_str}}
|
590
600
|
headers << %{Content-Type: #{content_type}}
|
601
|
+
elsif attr.is_a?(Hash)
|
602
|
+
h = attr
|
603
|
+
value = h[:content]
|
604
|
+
h.each do |h_key, h_val|
|
605
|
+
headers << %{#{h_key}: #{h_val}} if h_key != :content
|
606
|
+
end
|
607
|
+
remember_pos(value) if Message.file?(value)
|
591
608
|
else
|
592
609
|
headers << %{Content-Disposition: form-data; name="#{attr}"}
|
593
610
|
end
|
@@ -601,7 +618,8 @@ module HTTP
|
|
601
618
|
|
602
619
|
def params_from_file(value)
|
603
620
|
params = {}
|
604
|
-
|
621
|
+
path = value.respond_to?(:path) ? value.path : nil
|
622
|
+
params['filename'] = File.basename(path || '')
|
605
623
|
# Creation time is not available from File::Stat
|
606
624
|
if value.respond_to?(:mtime)
|
607
625
|
params['modification-date'] = value.mtime.rfc822
|
@@ -724,9 +742,9 @@ module HTTP
|
|
724
742
|
end
|
725
743
|
|
726
744
|
# Returns true if the given HTTP version allows keep alive connection.
|
727
|
-
# version::
|
745
|
+
# version:: String
|
728
746
|
def keep_alive_enabled?(version)
|
729
|
-
version >= 1.1
|
747
|
+
version >= '1.1'
|
730
748
|
end
|
731
749
|
|
732
750
|
# Returns true if the given query (or body) has a multiple parameter.
|
@@ -736,15 +754,14 @@ module HTTP
|
|
736
754
|
|
737
755
|
# Returns true if the given object is a File. In HTTPClient, a file is;
|
738
756
|
# * must respond to :read for retrieving String chunks.
|
739
|
-
# * must respond to :path and returns a path for Content-Disposition.
|
740
757
|
# * must respond to :pos and :pos= to rewind for reading.
|
741
758
|
# Rewinding is only needed for following HTTP redirect. Some IO impl
|
742
759
|
# defines :pos= but raises an Exception for pos= such as StringIO
|
743
760
|
# but there's no problem as far as using it for non-following methods
|
744
761
|
# (get/post/etc.)
|
745
762
|
def file?(obj)
|
746
|
-
obj.respond_to?(:read) and obj.respond_to?(:
|
747
|
-
obj.respond_to?(:pos
|
763
|
+
obj.respond_to?(:read) and obj.respond_to?(:pos) and
|
764
|
+
obj.respond_to?(:pos=)
|
748
765
|
end
|
749
766
|
|
750
767
|
def create_query_part_str(query) # :nodoc:
|
@@ -758,7 +775,7 @@ module HTTP
|
|
758
775
|
end
|
759
776
|
|
760
777
|
def escape_query(query) # :nodoc:
|
761
|
-
query.collect { |attr, value|
|
778
|
+
query.sort_by { |attr, value| attr.to_s }.collect { |attr, value|
|
762
779
|
if value.respond_to?(:read)
|
763
780
|
value = value.read
|
764
781
|
end
|
@@ -768,9 +785,36 @@ module HTTP
|
|
768
785
|
|
769
786
|
# from CGI.escape
|
770
787
|
def escape(str) # :nodoc:
|
771
|
-
|
772
|
-
|
773
|
-
|
788
|
+
if defined?(Encoding::ASCII_8BIT)
|
789
|
+
str.dup.force_encoding(Encoding::ASCII_8BIT).gsub(/([^ a-zA-Z0-9_.-]+)/) {
|
790
|
+
'%' + $1.unpack('H2' * $1.bytesize).join('%').upcase
|
791
|
+
}.tr(' ', '+')
|
792
|
+
else
|
793
|
+
str.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
|
794
|
+
'%' + $1.unpack('H2' * $1.bytesize).join('%').upcase
|
795
|
+
}.tr(' ', '+')
|
796
|
+
end
|
797
|
+
end
|
798
|
+
|
799
|
+
# from CGI.parse
|
800
|
+
def parse(query)
|
801
|
+
params = Hash.new([].freeze)
|
802
|
+
query.split(/[&;]/n).each do |pairs|
|
803
|
+
key, value = pairs.split('=',2).collect{|v| unescape(v) }
|
804
|
+
if params.has_key?(key)
|
805
|
+
params[key].push(value)
|
806
|
+
else
|
807
|
+
params[key] = [value]
|
808
|
+
end
|
809
|
+
end
|
810
|
+
params
|
811
|
+
end
|
812
|
+
|
813
|
+
# from CGI.unescape
|
814
|
+
def unescape(string)
|
815
|
+
string.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n) do
|
816
|
+
[$1.delete('%')].pack('H*')
|
817
|
+
end
|
774
818
|
end
|
775
819
|
end
|
776
820
|
|
@@ -813,13 +857,24 @@ module HTTP
|
|
813
857
|
@header.body_size = @body.size if @header
|
814
858
|
end
|
815
859
|
|
816
|
-
# Returns HTTP version in a HTTP header.
|
817
|
-
def
|
860
|
+
# Returns HTTP version in a HTTP header. String.
|
861
|
+
def http_version
|
818
862
|
@header.http_version
|
819
863
|
end
|
820
864
|
|
821
|
-
# Sets HTTP version in a HTTP header.
|
865
|
+
# Sets HTTP version in a HTTP header. String.
|
866
|
+
def http_version=(http_version)
|
867
|
+
@header.http_version = http_version
|
868
|
+
end
|
869
|
+
|
870
|
+
VERSION_WARNING = 'Message#version (Float) is deprecated. Use Message#http_version (String) instead.'
|
871
|
+
def version
|
872
|
+
warn(VERSION_WARNING)
|
873
|
+
@header.http_version.to_f
|
874
|
+
end
|
875
|
+
|
822
876
|
def version=(version)
|
877
|
+
warn(VERSION_WARNING)
|
823
878
|
@header.http_version = version
|
824
879
|
end
|
825
880
|
|
data/lib/httpclient/session.rb
CHANGED
@@ -13,6 +13,7 @@
|
|
13
13
|
require 'socket'
|
14
14
|
require 'thread'
|
15
15
|
require 'stringio'
|
16
|
+
require 'zlib'
|
16
17
|
|
17
18
|
require 'httpclient/timeout'
|
18
19
|
require 'httpclient/ssl_config'
|
@@ -106,6 +107,8 @@ class HTTPClient
|
|
106
107
|
|
107
108
|
attr_reader :test_loopback_http_response
|
108
109
|
|
110
|
+
attr_accessor :transparent_gzip_decompression
|
111
|
+
|
109
112
|
def initialize(client)
|
110
113
|
@client = client
|
111
114
|
@proxy = client.proxy
|
@@ -128,6 +131,7 @@ class HTTPClient
|
|
128
131
|
@ssl_config = nil
|
129
132
|
@test_loopback_http_response = []
|
130
133
|
|
134
|
+
@transparent_gzip_decompression = false
|
131
135
|
@sess_pool = []
|
132
136
|
@sess_pool_mutex = Mutex.new
|
133
137
|
end
|
@@ -165,6 +169,16 @@ class HTTPClient
|
|
165
169
|
add_cached_session(sess)
|
166
170
|
end
|
167
171
|
|
172
|
+
def invalidate(site)
|
173
|
+
@sess_pool_mutex.synchronize do
|
174
|
+
@sess_pool.each do |sess|
|
175
|
+
if sess.dest == site
|
176
|
+
sess.invalidate
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
168
182
|
private
|
169
183
|
|
170
184
|
def open(uri, via_proxy = false)
|
@@ -185,6 +199,7 @@ class HTTPClient
|
|
185
199
|
sess.ssl_config = @ssl_config
|
186
200
|
sess.debug_dev = @debug_dev
|
187
201
|
sess.test_loopback_http_response = @test_loopback_http_response
|
202
|
+
sess.transparent_gzip_decompression = @transparent_gzip_decompression
|
188
203
|
end
|
189
204
|
sess
|
190
205
|
end
|
@@ -198,6 +213,7 @@ class HTTPClient
|
|
198
213
|
@sess_pool.clear
|
199
214
|
end
|
200
215
|
|
216
|
+
# This method might not work as you expected...
|
201
217
|
def close(dest)
|
202
218
|
if cached = get_cached_session(dest)
|
203
219
|
cached.close
|
@@ -212,7 +228,9 @@ class HTTPClient
|
|
212
228
|
@sess_pool_mutex.synchronize do
|
213
229
|
new_pool = []
|
214
230
|
@sess_pool.each do |s|
|
215
|
-
if s.
|
231
|
+
if s.invalidated?
|
232
|
+
s.close # close & remove from the pool
|
233
|
+
elsif !cached && s.dest.match(uri)
|
216
234
|
cached = s
|
217
235
|
else
|
218
236
|
new_pool << s
|
@@ -435,7 +453,15 @@ class HTTPClient
|
|
435
453
|
private
|
436
454
|
|
437
455
|
def debug(str)
|
438
|
-
|
456
|
+
if str && @debug_dev
|
457
|
+
if str.index("\0")
|
458
|
+
require 'hexdump'
|
459
|
+
str.force_encoding('BINARY') if str.respond_to?(:force_encoding)
|
460
|
+
@debug_dev << HexDump.encode(str).join("\n")
|
461
|
+
else
|
462
|
+
@debug_dev << str
|
463
|
+
end
|
464
|
+
end
|
439
465
|
end
|
440
466
|
end
|
441
467
|
|
@@ -459,6 +485,7 @@ class HTTPClient
|
|
459
485
|
# Manages a HTTP session with a Site.
|
460
486
|
class Session
|
461
487
|
include HTTPClient::Timeout
|
488
|
+
include Util
|
462
489
|
|
463
490
|
# Destination site
|
464
491
|
attr_reader :dest
|
@@ -482,9 +509,12 @@ class HTTPClient
|
|
482
509
|
attr_reader :ssl_peer_cert
|
483
510
|
attr_accessor :test_loopback_http_response
|
484
511
|
|
512
|
+
attr_accessor :transparent_gzip_decompression
|
513
|
+
|
485
514
|
def initialize(client, dest, agent_name, from)
|
486
515
|
@client = client
|
487
516
|
@dest = dest
|
517
|
+
@invalidated = false
|
488
518
|
@proxy = nil
|
489
519
|
@socket_sync = true
|
490
520
|
@requested_version = nil
|
@@ -515,12 +545,15 @@ class HTTPClient
|
|
515
545
|
|
516
546
|
@socket = nil
|
517
547
|
@readbuf = nil
|
548
|
+
|
549
|
+
@transparent_gzip_decompression = false
|
518
550
|
end
|
519
551
|
|
520
552
|
# Send a request to the server
|
521
553
|
def query(req)
|
522
554
|
connect if @state == :INIT
|
523
|
-
|
555
|
+
# Use absolute URI (not absolute path) iif via proxy AND not HTTPS.
|
556
|
+
req.header.request_absolute_uri = !@proxy.nil? and !https?(@dest)
|
524
557
|
begin
|
525
558
|
timeout(@send_timeout, SendTimeoutError) do
|
526
559
|
set_header(req)
|
@@ -529,14 +562,16 @@ class HTTPClient
|
|
529
562
|
@socket.flush unless @socket_sync
|
530
563
|
end
|
531
564
|
rescue Errno::ECONNABORTED, Errno::ECONNRESET, Errno::EPIPE
|
565
|
+
# JRuby can raise IOError instead of ECONNRESET for now
|
532
566
|
close
|
533
|
-
raise KeepAliveDisconnected.new
|
567
|
+
raise KeepAliveDisconnected.new(self)
|
534
568
|
rescue HTTPClient::TimeoutError
|
535
569
|
close
|
536
570
|
raise
|
537
571
|
rescue
|
572
|
+
close
|
538
573
|
if SSLEnabled and $!.is_a?(OpenSSL::SSL::SSLError)
|
539
|
-
raise KeepAliveDisconnected.new
|
574
|
+
raise KeepAliveDisconnected.new(self)
|
540
575
|
else
|
541
576
|
raise
|
542
577
|
end
|
@@ -560,6 +595,14 @@ class HTTPClient
|
|
560
595
|
@state == :INIT
|
561
596
|
end
|
562
597
|
|
598
|
+
def invalidate
|
599
|
+
@invalidated = true
|
600
|
+
end
|
601
|
+
|
602
|
+
def invalidated?
|
603
|
+
@invalidated
|
604
|
+
end
|
605
|
+
|
563
606
|
def get_header
|
564
607
|
begin
|
565
608
|
if @state != :META
|
@@ -585,6 +628,19 @@ class HTTPClient
|
|
585
628
|
begin
|
586
629
|
read_header if @state == :META
|
587
630
|
return nil if @state != :DATA
|
631
|
+
if @gzipped and @transparent_gzip_decompression
|
632
|
+
# zlib itself has a functionality to decompress gzip stream.
|
633
|
+
# - zlib 1.2.5 Manual
|
634
|
+
# http://www.zlib.net/manual.html#Advanced
|
635
|
+
# > windowBits can also be greater than 15 for optional gzip decoding. Add 32 to
|
636
|
+
# > windowBits to enable zlib and gzip decoding with automatic header detection,
|
637
|
+
# > or add 16 to decode only the gzip format
|
638
|
+
inflate_stream = Zlib::Inflate.new(Zlib::MAX_WBITS + 32)
|
639
|
+
original_block = block
|
640
|
+
block = Proc.new { |buf|
|
641
|
+
original_block.call(inflate_stream.inflate(buf))
|
642
|
+
}
|
643
|
+
end
|
588
644
|
if @chunked
|
589
645
|
read_body_chunked(&block)
|
590
646
|
elsif @content_length
|
@@ -611,7 +667,7 @@ class HTTPClient
|
|
611
667
|
def set_header(req)
|
612
668
|
if @requested_version
|
613
669
|
if /^(?:HTTP\/|)(\d+.\d+)$/ =~ @requested_version
|
614
|
-
req.
|
670
|
+
req.http_version = $1
|
615
671
|
end
|
616
672
|
end
|
617
673
|
if @agent_name
|
@@ -620,6 +676,9 @@ class HTTPClient
|
|
620
676
|
if @from
|
621
677
|
req.header.set('From', @from)
|
622
678
|
end
|
679
|
+
if @transparent_gzip_decompression
|
680
|
+
req.header.set('Accept-Encoding', 'gzip,deflate')
|
681
|
+
end
|
623
682
|
req.header.set('Date', Time.now.httpdate)
|
624
683
|
end
|
625
684
|
|
@@ -630,7 +689,7 @@ class HTTPClient
|
|
630
689
|
begin
|
631
690
|
timeout(@connect_timeout, ConnectTimeoutError) do
|
632
691
|
@socket = create_socket(site)
|
633
|
-
if @dest
|
692
|
+
if https?(@dest)
|
634
693
|
if @socket.is_a?(LoopBackSocket)
|
635
694
|
connect_ssl_proxy(@socket, URI.parse(@dest.to_s)) if @proxy
|
636
695
|
else
|
@@ -704,7 +763,7 @@ class HTTPClient
|
|
704
763
|
@socket.flush unless @socket_sync
|
705
764
|
res = HTTP::Message.new_response('')
|
706
765
|
parse_header
|
707
|
-
res.
|
766
|
+
res.http_version, res.status, res.reason = @version, @status, @reason
|
708
767
|
@headers.each do |key, value|
|
709
768
|
res.header.set(key, value)
|
710
769
|
end
|
@@ -723,14 +782,13 @@ class HTTPClient
|
|
723
782
|
def read_header
|
724
783
|
@content_length = nil
|
725
784
|
@chunked = false
|
785
|
+
@gzipped = false
|
726
786
|
@chunk_length = 0
|
727
787
|
parse_header
|
728
|
-
|
729
|
-
# Head of the request has been parsed.
|
788
|
+
# Header of the request has been parsed.
|
730
789
|
@state = :DATA
|
731
790
|
req = @requests.shift
|
732
|
-
|
733
|
-
if req.header.request_method == 'HEAD'
|
791
|
+
if req.header.request_method == 'HEAD' or no_message_body?(@status)
|
734
792
|
@content_length = 0
|
735
793
|
if @next_connection
|
736
794
|
@state = :WAIT
|
@@ -744,11 +802,19 @@ class HTTPClient
|
|
744
802
|
StatusParseRegexp = %r(\AHTTP/(\d+\.\d+)\s+(\d\d\d)\s*([^\r\n]+)?\r?\n\z)
|
745
803
|
def parse_header
|
746
804
|
timeout(@receive_timeout, ReceiveTimeoutError) do
|
805
|
+
initial_line = nil
|
747
806
|
begin
|
748
807
|
initial_line = @socket.gets("\n")
|
749
808
|
if initial_line.nil?
|
750
|
-
|
809
|
+
close
|
810
|
+
raise KeepAliveDisconnected.new(self)
|
751
811
|
end
|
812
|
+
rescue Errno::ECONNABORTED, Errno::ECONNRESET, Errno::EPIPE, IOError
|
813
|
+
# JRuby can raise IOError instead of ECONNRESET for now
|
814
|
+
close
|
815
|
+
raise KeepAliveDisconnected.new(self)
|
816
|
+
end
|
817
|
+
begin
|
752
818
|
if StatusParseRegexp !~ initial_line
|
753
819
|
@version = '0.9'
|
754
820
|
@status = nil
|
@@ -759,7 +825,7 @@ class HTTPClient
|
|
759
825
|
break
|
760
826
|
end
|
761
827
|
@version, @status, @reason = $1, $2.to_i, $3
|
762
|
-
@next_connection = HTTP::Message.keep_alive_enabled?(@version
|
828
|
+
@next_connection = HTTP::Message.keep_alive_enabled?(@version)
|
763
829
|
@headers = []
|
764
830
|
while true
|
765
831
|
line = @socket.gets("\n")
|
@@ -768,18 +834,32 @@ class HTTPClient
|
|
768
834
|
end
|
769
835
|
line.chomp!
|
770
836
|
break if line.empty?
|
771
|
-
|
772
|
-
|
773
|
-
|
837
|
+
if line[0] == ?\ or line[0] == ?\t
|
838
|
+
last = @headers.last[1]
|
839
|
+
last << ' ' unless last.empty?
|
840
|
+
last << line.strip
|
841
|
+
else
|
842
|
+
key, value = line.strip.split(/\s*:\s*/, 2)
|
843
|
+
parse_keepalive_header(key, value)
|
844
|
+
@headers << [key, value]
|
845
|
+
end
|
774
846
|
end
|
775
847
|
end while (@version == '1.1' && @status == 100)
|
776
848
|
end
|
777
849
|
end
|
778
850
|
|
851
|
+
def no_message_body?(status)
|
852
|
+
!status.nil? && # HTTP/0.9
|
853
|
+
((status >= 100 && status < 200) || status == 204 || status == 304)
|
854
|
+
end
|
855
|
+
|
779
856
|
def parse_keepalive_header(key, value)
|
780
857
|
key = key.downcase
|
781
858
|
if key == 'content-length'
|
782
859
|
@content_length = value.to_i
|
860
|
+
elsif key == 'content-encoding' and ( value.downcase == 'gzip' or
|
861
|
+
value.downcase == 'x-gzip' or value.downcase == 'deflate' )
|
862
|
+
@gzipped = true
|
783
863
|
elsif key == 'transfer-encoding' and value.downcase == 'chunked'
|
784
864
|
@chunked = true
|
785
865
|
@chunk_length = 0
|
@@ -806,8 +886,8 @@ class HTTPClient
|
|
806
886
|
buf = nil
|
807
887
|
end
|
808
888
|
end
|
809
|
-
if buf && buf.
|
810
|
-
@content_length -= buf.
|
889
|
+
if buf && buf.bytesize > 0
|
890
|
+
@content_length -= buf.bytesize
|
811
891
|
yield buf
|
812
892
|
else
|
813
893
|
@content_length = 0
|
@@ -837,7 +917,7 @@ class HTTPClient
|
|
837
917
|
end
|
838
918
|
|
839
919
|
def read_body_rest
|
840
|
-
if @readbuf and @readbuf.
|
920
|
+
if @readbuf and @readbuf.bytesize > 0
|
841
921
|
yield @readbuf
|
842
922
|
@readbuf = nil
|
843
923
|
end
|
@@ -850,7 +930,7 @@ class HTTPClient
|
|
850
930
|
buf = nil
|
851
931
|
end
|
852
932
|
end
|
853
|
-
if buf && buf.
|
933
|
+
if buf && buf.bytesize > 0
|
854
934
|
yield buf
|
855
935
|
else
|
856
936
|
return
|