32
32
def self.parse(socket, datum)
33
# this will discard any trailing lines from the previous response if any.
34
until match = /^HTTP\/\d+\.\d+\s(\d{3})\s/.match(socket.readline); end
35
status = match[1].to_i
33
37
datum[:response] = {
36
:status => socket.read(12)[9, 11].to_i,
37
41
:remote_ip => socket.respond_to?(:remote_ip) && socket.remote_ip
39
socket.readline # read the rest of the status line and CRLF
41
until ((data = socket.readline).chop!).empty?
42
key, value = data.split(/:\s*/, 2)
43
datum[:response][:headers][key] = ([*datum[:response][:headers][key]] << value).compact.join(', ')
44
if key.casecmp('Content-Length') == 0
45
content_length = value.to_i
46
elsif (key.casecmp('Transfer-Encoding') == 0) && (value.casecmp('chunked') == 0)
47
transfer_encoding_chunked = true
44
parse_headers(socket, datum)
51
46
unless (['HEAD', 'CONNECT'].include?(datum[:method].to_s.upcase)) || NO_ENTITY.include?(datum[:response][:status])
53
# check to see if expects was set and matched
54
expected_status = !datum.has_key?(:expects) || [*datum[:expects]].include?(datum[:response][:status])
56
# if expects matched and there is a block, use it
57
if expected_status && datum.has_key?(:response_block)
58
if transfer_encoding_chunked
60
while (chunk_size = socket.readline.chop!.to_i(16)) > 0
61
datum[:response_block].call(socket.read(chunk_size + 2).chop!, nil, nil)
64
elsif remaining = content_length
48
if key = datum[:response][:headers].keys.detect {|k| k.casecmp('Transfer-Encoding') == 0 }
49
encodings = Utils.split_header_value(datum[:response][:headers][key])
50
if (encoding = encodings.last) && encoding.casecmp('chunked') == 0
51
transfer_encoding_chunked = true
53
datum[:response][:headers][key] = encodings.join(', ')
56
unless transfer_encoding_chunked
57
if key = datum[:response][:headers].keys.detect {|k| k.casecmp('Content-Length') == 0 }
58
content_length = datum[:response][:headers][key].to_i
62
# use :response_block unless :expects would fail
63
if response_block = datum[:response_block]
64
if datum[:middlewares].include?(Excon::Middleware::Expects) && datum[:expects] &&
65
!Array(datum[:expects]).include?(datum[:response][:status])
70
if transfer_encoding_chunked
73
while (chunk_size = socket.readline.chop!.to_i(16)) > 0
74
response_block.call(socket.read(chunk_size + 2).chop!, nil, nil)
77
while (chunk_size = socket.readline.chop!.to_i(16)) > 0
78
datum[:response][:body] << socket.read(chunk_size + 2).chop!
81
parse_headers(socket, datum) # merge trailers into headers
82
elsif remaining = content_length
65
84
while remaining > 0
66
datum[:response_block].call(socket.read([datum[:chunk_size], remaining].min), [remaining - datum[:chunk_size], 0].max, content_length)
85
response_block.call(socket.read([datum[:chunk_size], remaining].min), [remaining - datum[:chunk_size], 0].max, content_length)
67
86
remaining -= datum[:chunk_size]
70
while remaining = socket.read(datum[:chunk_size])
71
datum[:response_block].call(remaining, remaining.length, content_length)
74
else # no block or unexpected status
75
if transfer_encoding_chunked
76
while (chunk_size = socket.readline.chop!.to_i(16)) > 0
77
datum[:response][:body] << socket.read(chunk_size + 2).chop! # 2 == "/r/n".length
79
socket.read(2) # 2 == "/r/n".length
80
elsif remaining = content_length
81
89
while remaining > 0
82
90
datum[:response][:body] << socket.read([datum[:chunk_size], remaining].min)
83
91
remaining -= datum[:chunk_size]
96
while chunk = socket.read(datum[:chunk_size])
97
response_block.call(chunk, nil, nil)
86
100
datum[:response][:body] << socket.read
107
def self.parse_headers(socket, datum)
109
until (data = socket.readline.chop!).empty?
110
if !data.lstrip!.nil?
111
raise Excon::Errors::ResponseParseError, 'malformed header' unless last_key
112
# append to last_key's last value
113
datum[:response][:headers][last_key] << ' ' << data.rstrip
115
key, value = data.split(':', 2)
116
raise Excon::Errors::ResponseParseError, 'malformed header' unless value
117
# add key/value or append value to existing values
118
datum[:response][:headers][key] = ([datum[:response][:headers][key]] << value.strip).compact.join(', ')
93
124
def initialize(params={})