kronk 1.7.8 → 1.8.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.
- data/History.rdoc +32 -0
- data/Manifest.txt +5 -5
- data/README.rdoc +5 -3
- data/Rakefile +3 -4
- data/TODO.rdoc +22 -6
- data/lib/kronk.rb +13 -7
- data/lib/kronk/buffered_io.rb +26 -0
- data/lib/kronk/cmd.rb +58 -44
- data/lib/kronk/constants.rb +5 -6
- data/lib/kronk/diff.rb +12 -13
- data/lib/kronk/diff/output.rb +1 -1
- data/lib/kronk/http.rb +105 -0
- data/lib/kronk/player.rb +74 -82
- data/lib/kronk/player/benchmark.rb +34 -35
- data/lib/kronk/player/stream.rb +6 -6
- data/lib/kronk/player/suite.rb +17 -16
- data/lib/kronk/player/tsv.rb +51 -0
- data/lib/kronk/queue_runner.rb +126 -102
- data/lib/kronk/request.rb +144 -82
- data/lib/kronk/response.rb +520 -202
- data/lib/kronk/test/helper_methods.rb +1 -1
- data/test/mocks/200_gzip.txt +0 -0
- data/test/mocks/200_inflate.txt +0 -0
- data/test/test_assertions.rb +1 -1
- data/test/test_cmd.rb +18 -11
- data/test/test_diff.rb +39 -0
- data/test/test_helper.rb +18 -14
- data/test/test_helper_methods.rb +5 -4
- data/test/test_kronk.rb +8 -11
- data/test/test_player.rb +67 -123
- data/test/test_request.rb +8 -14
- data/test/test_response.rb +228 -60
- metadata +20 -31
- data/lib/kronk/async.rb +0 -118
- data/lib/kronk/async/em_ext.rb +0 -34
- data/lib/kronk/async/request.rb +0 -73
- data/lib/kronk/async/response.rb +0 -70
- data/lib/kronk/player/output.rb +0 -49
data/lib/kronk/request.rb
CHANGED
@@ -6,7 +6,7 @@ class Kronk
|
|
6
6
|
class Request
|
7
7
|
|
8
8
|
# Raised by Request.parse when parsing invalid http request string.
|
9
|
-
class ParseError < Kronk::
|
9
|
+
class ParseError < Kronk::Error; end
|
10
10
|
|
11
11
|
# Matches the first line of an http request string or a fully
|
12
12
|
# qualified URL.
|
@@ -46,20 +46,22 @@ class Kronk
|
|
46
46
|
# Build the URI to use for the request from the given uri or
|
47
47
|
# path and options.
|
48
48
|
|
49
|
-
def self.build_uri uri,
|
50
|
-
uri ||=
|
51
|
-
suffix =
|
49
|
+
def self.build_uri uri, opts={}
|
50
|
+
uri ||= opts[:host] || Kronk.config[:default_host]
|
51
|
+
suffix = opts[:uri_suffix]
|
52
52
|
|
53
53
|
uri = "http://#{uri}" unless uri.to_s =~ %r{^(\w+://|/)}
|
54
54
|
uri = "#{uri}#{suffix}" if suffix
|
55
55
|
uri = URI.parse uri unless URI === uri
|
56
56
|
uri = URI.parse(Kronk.config[:default_host]) + uri unless uri.host
|
57
57
|
|
58
|
-
if
|
59
|
-
query = build_query
|
58
|
+
if opts[:query]
|
59
|
+
query = build_query opts[:query]
|
60
60
|
uri.query = [uri.query, query].compact.join "&"
|
61
61
|
end
|
62
62
|
|
63
|
+
uri.path = "/" if uri.path.empty?
|
64
|
+
|
63
65
|
uri
|
64
66
|
end
|
65
67
|
|
@@ -185,6 +187,18 @@ class Kronk
|
|
185
187
|
end
|
186
188
|
|
187
189
|
|
190
|
+
class << self
|
191
|
+
%w{get post put delete trace head options}.each do |name|
|
192
|
+
class_eval <<-"END"
|
193
|
+
def #{name} uri, opts={}, &block
|
194
|
+
opts[:http_method] = "#{name}"
|
195
|
+
new(uri, opts).retrieve(&block)
|
196
|
+
end
|
197
|
+
END
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
|
188
202
|
attr_accessor :body, :headers, :proxy, :response, :timeout
|
189
203
|
|
190
204
|
attr_reader :http_method, :uri, :use_cookies
|
@@ -199,34 +213,42 @@ class Kronk
|
|
199
213
|
# :headers:: Hash - extra headers to pass to the request
|
200
214
|
# :http_method:: Symbol - the http method to use; defaults to :get
|
201
215
|
# :proxy:: Hash/String - http proxy to use; defaults to {}
|
216
|
+
# :accept_encoding:: Array/String - list of encodings the server can return
|
202
217
|
#
|
203
218
|
# Note: if no http method is specified and data is given, will default
|
204
219
|
# to using a post request.
|
205
220
|
|
206
|
-
def initialize uri,
|
207
|
-
@auth =
|
221
|
+
def initialize uri, opts={}
|
222
|
+
@auth = opts[:auth]
|
208
223
|
|
209
224
|
@body = nil
|
210
|
-
@body = self.class.build_query
|
225
|
+
@body = self.class.build_query opts[:data] if opts[:data]
|
211
226
|
|
227
|
+
@connection = nil
|
212
228
|
@response = nil
|
213
229
|
@_req = nil
|
214
|
-
@_res = nil
|
215
230
|
|
216
|
-
@headers =
|
217
|
-
|
231
|
+
@headers = opts[:headers] || {}
|
232
|
+
|
233
|
+
@headers["Accept-Encoding"] = [
|
234
|
+
@headers["Accept-Encoding"].to_s.split(","),
|
235
|
+
Array(opts[:accept_encoding])
|
236
|
+
].flatten.compact.uniq.join(",")
|
237
|
+
@headers.delete "Accept-Encoding" if @headers["Accept-Encoding"].empty?
|
238
|
+
|
239
|
+
@timeout = opts[:timeout] || Kronk.config[:timeout]
|
218
240
|
|
219
|
-
@uri = self.class.build_uri uri,
|
241
|
+
@uri = self.class.build_uri uri, opts
|
220
242
|
|
221
|
-
@proxy =
|
243
|
+
@proxy = opts[:proxy] || {}
|
222
244
|
@proxy = {:host => @proxy} unless Hash === @proxy
|
223
245
|
|
224
|
-
self.user_agent ||=
|
246
|
+
self.user_agent ||= opts[:user_agent]
|
225
247
|
|
226
|
-
self.http_method =
|
248
|
+
self.http_method = opts[:http_method] || (@body ? "POST" : "GET")
|
227
249
|
|
228
|
-
self.use_cookies =
|
229
|
-
!
|
250
|
+
self.use_cookies = opts.has_key?(:no_cookies) ?
|
251
|
+
!opts[:no_cookies] : Kronk.config[:use_cookies]
|
230
252
|
end
|
231
253
|
|
232
254
|
|
@@ -247,59 +269,33 @@ class Kronk
|
|
247
269
|
|
248
270
|
|
249
271
|
##
|
250
|
-
#
|
251
|
-
|
252
|
-
def cookie= cookie_str
|
253
|
-
@headers['Cookie'] = cookie_str if @use_cookies
|
254
|
-
end
|
255
|
-
|
272
|
+
# Reference to the HTTP connection instance.
|
256
273
|
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
def http_method= new_verb
|
261
|
-
@http_method = new_verb.to_s.upcase
|
262
|
-
end
|
263
|
-
|
264
|
-
|
265
|
-
##
|
266
|
-
# Returns the HTTP request object.
|
267
|
-
|
268
|
-
def http_request
|
269
|
-
req = VanillaRequest.new @http_method, @uri.request_uri, @headers
|
274
|
+
def connection
|
275
|
+
return @connection if @connection
|
276
|
+
http_class = http_proxy @proxy[:host], @proxy
|
270
277
|
|
271
|
-
|
272
|
-
|
278
|
+
@connection = http_class.new @uri.host, @uri.port
|
279
|
+
@connection.open_timeout = @connection.read_timeout = @timeout if @timeout
|
280
|
+
@connection.use_ssl = true if @uri.scheme =~ /^https$/
|
273
281
|
|
274
|
-
|
282
|
+
@connection
|
275
283
|
end
|
276
284
|
|
277
285
|
|
278
286
|
##
|
279
|
-
#
|
280
|
-
# The proxy_opts arg can be a uri String or a Hash with the :address key
|
281
|
-
# and optional :username and :password keys.
|
282
|
-
|
283
|
-
def http_proxy addr, opts={}
|
284
|
-
return Net::HTTP unless addr
|
285
|
-
|
286
|
-
host, port = addr.split ":"
|
287
|
-
port ||= opts[:port] || 8080
|
288
|
-
|
289
|
-
user = opts[:username]
|
290
|
-
pass = opts[:password]
|
291
|
-
|
292
|
-
Kronk::Cmd.verbose "Using proxy #{addr}\n" if host
|
287
|
+
# Assigns the cookie string.
|
293
288
|
|
294
|
-
|
289
|
+
def cookie= cookie_str
|
290
|
+
@headers['Cookie'] = cookie_str if @use_cookies
|
295
291
|
end
|
296
292
|
|
297
293
|
|
298
294
|
##
|
299
|
-
#
|
295
|
+
# Assigns the http method.
|
300
296
|
|
301
|
-
def
|
302
|
-
@
|
297
|
+
def http_method= new_verb
|
298
|
+
@http_method = new_verb.to_s.upcase
|
303
299
|
end
|
304
300
|
|
305
301
|
|
@@ -354,41 +350,73 @@ class Kronk
|
|
354
350
|
|
355
351
|
|
356
352
|
##
|
357
|
-
# Retrieve this requests' response.
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
@_req.use_ssl = true if @uri.scheme =~ /^https$/
|
366
|
-
|
367
|
-
elapsed_time = nil
|
368
|
-
socket = nil
|
369
|
-
socket_io = nil
|
353
|
+
# Retrieve this requests' response. Returns a Kronk::Response once the
|
354
|
+
# full HTTP response has been read. If a block is given, will yield
|
355
|
+
# the response and body chunks as they get received.
|
356
|
+
#
|
357
|
+
# Note: Block will yield the full body if the response is compressed
|
358
|
+
# using Deflate as the Deflate format does not support streaming.
|
359
|
+
#
|
360
|
+
# Options are passed directly to the Kronk::Response constructor.
|
370
361
|
|
371
|
-
|
372
|
-
|
373
|
-
|
362
|
+
def retrieve opts={}, &block
|
363
|
+
start_time = nil
|
364
|
+
opts = opts.merge :request => self
|
374
365
|
|
366
|
+
@response = connection.start do |http|
|
375
367
|
start_time = Time.now
|
376
|
-
res = http.request
|
377
|
-
|
378
|
-
|
368
|
+
res = http.request http_request, @body, opts, &block
|
369
|
+
res.body # make sure to read the full body from io
|
370
|
+
res.time = Time.now - start_time
|
371
|
+
res.request = self
|
379
372
|
res
|
380
373
|
end
|
381
374
|
|
382
|
-
|
383
|
-
|
375
|
+
@response
|
376
|
+
end
|
377
|
+
|
384
378
|
|
385
|
-
|
386
|
-
|
379
|
+
##
|
380
|
+
# Retrieve this requests' response but only reads HTTP headers before
|
381
|
+
# returning and leaves the connection open.
|
382
|
+
#
|
383
|
+
# Options are passed directly to the Kronk::Response constructor.
|
384
|
+
#
|
385
|
+
# Connection must be closed using:
|
386
|
+
# request.connection.finish
|
387
|
+
|
388
|
+
def stream opts={}
|
389
|
+
opts = opts.merge :request => self
|
390
|
+
http = connection.started? ? connection : connection.start
|
391
|
+
@response = http.request http_request, @body, opts
|
392
|
+
@response.request = self
|
387
393
|
|
388
394
|
@response
|
389
395
|
end
|
390
396
|
|
391
397
|
|
398
|
+
##
|
399
|
+
# Returns this Request instance as an options hash.
|
400
|
+
|
401
|
+
def to_hash
|
402
|
+
hash = {
|
403
|
+
:host => "#{@uri.scheme}://#{@uri.host}:#{@uri.port}",
|
404
|
+
:uri_suffix => @uri.request_uri,
|
405
|
+
:user_agent => self.user_agent,
|
406
|
+
:timeout => @timeout,
|
407
|
+
:http_method => self.http_method,
|
408
|
+
:no_cookies => !self.use_cookies
|
409
|
+
}
|
410
|
+
|
411
|
+
hash[:auth] = @auth if @auth
|
412
|
+
hash[:data] = @body if @body
|
413
|
+
hash[:headers] = @headers unless @headers.empty?
|
414
|
+
hash[:proxy] = @proxy unless @proxy.empty?
|
415
|
+
|
416
|
+
hash
|
417
|
+
end
|
418
|
+
|
419
|
+
|
392
420
|
##
|
393
421
|
# Returns the raw HTTP request String.
|
394
422
|
|
@@ -396,7 +424,7 @@ class Kronk
|
|
396
424
|
out = "#{@http_method} #{@uri.request_uri} HTTP/1.1\r\n"
|
397
425
|
out << "host: #{@uri.host}:#{@uri.port}\r\n"
|
398
426
|
|
399
|
-
|
427
|
+
http_request.each do |name, value|
|
400
428
|
out << "#{name}: #{value}\r\n" unless name =~ /host/i
|
401
429
|
end
|
402
430
|
|
@@ -413,6 +441,40 @@ class Kronk
|
|
413
441
|
end
|
414
442
|
|
415
443
|
|
444
|
+
##
|
445
|
+
# Returns the HTTP request object.
|
446
|
+
|
447
|
+
def http_request
|
448
|
+
req = VanillaRequest.new @http_method, @uri.request_uri, @headers
|
449
|
+
|
450
|
+
req.basic_auth @auth[:username], @auth[:password] if
|
451
|
+
@auth && @auth[:username]
|
452
|
+
|
453
|
+
req
|
454
|
+
end
|
455
|
+
|
456
|
+
|
457
|
+
##
|
458
|
+
# Assign the use of a proxy.
|
459
|
+
# The proxy_opts arg can be a uri String or a Hash with the :address key
|
460
|
+
# and optional :username and :password keys.
|
461
|
+
|
462
|
+
def http_proxy addr, opts={}
|
463
|
+
return Kronk::HTTP unless addr
|
464
|
+
|
465
|
+
host, port = addr.split ":"
|
466
|
+
port ||= opts[:port] || 8080
|
467
|
+
|
468
|
+
user = opts[:username]
|
469
|
+
pass = opts[:password]
|
470
|
+
|
471
|
+
Kronk::Cmd.verbose "Using proxy #{addr}\n" if host
|
472
|
+
|
473
|
+
Kronk::HTTP::Proxy host, port, user, pass
|
474
|
+
end
|
475
|
+
|
476
|
+
|
477
|
+
|
416
478
|
##
|
417
479
|
# Allow any http method to be sent
|
418
480
|
|
data/lib/kronk/response.rb
CHANGED
@@ -1,25 +1,12 @@
|
|
1
1
|
class Kronk
|
2
2
|
|
3
|
-
##
|
4
|
-
# Mock File IO to allow rewinding on Windows platforms.
|
5
|
-
|
6
|
-
class WinFileIO < StringIO
|
7
|
-
attr_accessor :path
|
8
|
-
|
9
|
-
def initialize path, str=""
|
10
|
-
@path = path
|
11
|
-
super str
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
|
16
3
|
##
|
17
4
|
# Standard Kronk response object.
|
18
5
|
|
19
6
|
class Response
|
20
7
|
|
21
|
-
class MissingParser < Kronk::
|
22
|
-
class InvalidParser < Kronk::
|
8
|
+
class MissingParser < Kronk::Error; end
|
9
|
+
class InvalidParser < Kronk::Error; end
|
23
10
|
|
24
11
|
|
25
12
|
ENCODING_MATCHER = /(^|;\s?)charset=(.*?)\s*(;|$)/
|
@@ -27,68 +14,145 @@ class Kronk
|
|
27
14
|
##
|
28
15
|
# Read http response from a file and return a Kronk::Response instance.
|
29
16
|
|
30
|
-
def self.read_file path
|
31
|
-
file
|
32
|
-
resp
|
33
|
-
resp.
|
17
|
+
def self.read_file path, opts={}, &block
|
18
|
+
file = File.open(path, "rb")
|
19
|
+
resp = new(file, opts)
|
20
|
+
resp.body(&block)
|
34
21
|
file.close
|
35
22
|
|
36
23
|
resp
|
37
24
|
end
|
38
25
|
|
39
26
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
alias to_s raw
|
27
|
+
attr_reader :code, :io, :cookies, :headers
|
28
|
+
attr_accessor :read, :request, :stringify_opts, :time
|
44
29
|
|
45
30
|
##
|
46
31
|
# Create a new Response object from a String or IO.
|
32
|
+
# Options supported are:
|
33
|
+
# :request:: The Kronk::Request instance for this response.
|
34
|
+
# :timeout:: The read timeout value in seconds.
|
35
|
+
# :no_body:: Ignore reading the body of the response.
|
36
|
+
# :force_gzip:: Force decoding body with gzip.
|
37
|
+
# :force_inflate:: Force decoding body with inflate.
|
38
|
+
# :allow_headless:: Allow headless responses (won't raise for invalid HTTP).
|
47
39
|
|
48
|
-
def initialize
|
49
|
-
|
50
|
-
|
40
|
+
def initialize input, opts={}, &block
|
41
|
+
@request = opts[:request]
|
42
|
+
@headers = {}
|
43
|
+
@encoding = @parser = @body = @gzip = @gzip_io = nil
|
51
44
|
|
52
|
-
|
53
|
-
@_res, debug_io = res, io
|
54
|
-
else
|
55
|
-
@_res, debug_io = request_from_io(io)
|
56
|
-
end
|
45
|
+
@headless = false
|
57
46
|
|
58
|
-
@
|
47
|
+
@stringify_opts = {}
|
59
48
|
|
49
|
+
@raw = ""
|
60
50
|
@time = 0
|
61
51
|
|
62
|
-
|
63
|
-
|
52
|
+
input ||= ""
|
53
|
+
input = StringIO.new(input) if String === input
|
64
54
|
|
65
|
-
@
|
55
|
+
@io = BufferedIO === input ? input : BufferedIO.new(input)
|
66
56
|
|
67
|
-
@
|
68
|
-
@
|
57
|
+
@io.raw_output = @raw
|
58
|
+
@io.response = self
|
59
|
+
@io.read_timeout = opts[:timeout] if opts[:timeout]
|
69
60
|
|
70
|
-
|
61
|
+
allow_headless = opts.has_key?(:allow_headless) ?
|
62
|
+
opts[:allow_headless] :
|
63
|
+
headless_ok?(@io.io)
|
71
64
|
|
72
|
-
|
73
|
-
@uri = URI.parse io.path if File === io
|
65
|
+
response_from_io @io, allow_headless
|
74
66
|
|
75
|
-
@
|
67
|
+
@cookies = []
|
68
|
+
|
69
|
+
if URI::HTTP === uri
|
70
|
+
jar = CookieJar::Jar.new
|
71
|
+
jar.set_cookies_from_headers uri, @headers
|
72
|
+
|
73
|
+
jar.to_a.each do |cookie|
|
74
|
+
@cookies << cookie.to_hash
|
75
|
+
Kronk.cookie_jar.add_cookie cookie unless opts[:no_cookies]
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
self.gzip = opts[:force_gzip]
|
80
|
+
self.inflate = opts[:force_inflate]
|
81
|
+
gzip?
|
82
|
+
deflated?
|
83
|
+
|
84
|
+
@read = !!opts[:no_body]
|
85
|
+
body(&block) if block_given?
|
76
86
|
end
|
77
87
|
|
78
88
|
|
79
89
|
##
|
80
|
-
# Accessor for the
|
90
|
+
# Accessor for the HTTP headers []
|
81
91
|
|
82
92
|
def [] key
|
83
|
-
@
|
93
|
+
@headers[key.to_s.downcase]
|
84
94
|
end
|
85
95
|
|
86
96
|
|
87
97
|
##
|
88
|
-
#
|
98
|
+
# Setter for the HTTP headers []
|
89
99
|
|
90
100
|
def []= key, value
|
91
|
-
@
|
101
|
+
@headers[key.to_s.downcase] = value
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
##
|
106
|
+
# Returns the body of the response. Will wait for the socket to finish
|
107
|
+
# reading if the body hasn't finished loading.
|
108
|
+
#
|
109
|
+
# If a block is given and the body hasn't been read yet, will iterate
|
110
|
+
# yielding a chunk of the body as it becomes available.
|
111
|
+
#
|
112
|
+
# Note: Block will yield the full body if the response is compressed
|
113
|
+
# using Deflate as the Deflate format does not support streaming.
|
114
|
+
#
|
115
|
+
# resp = Kronk::Response.new io
|
116
|
+
# resp.body do |chunk|
|
117
|
+
# # handle stream
|
118
|
+
# end
|
119
|
+
|
120
|
+
def body &block
|
121
|
+
return @body if @read
|
122
|
+
|
123
|
+
raise IOError, 'Socket closed.' if @io.closed?
|
124
|
+
|
125
|
+
error = false
|
126
|
+
last_pos = 0
|
127
|
+
|
128
|
+
begin
|
129
|
+
read_body do |chunk|
|
130
|
+
chunk = unzip chunk if gzip?
|
131
|
+
|
132
|
+
try_force_encoding chunk
|
133
|
+
(@body ||= "") << chunk
|
134
|
+
yield chunk if block_given? && !deflated?
|
135
|
+
end
|
136
|
+
|
137
|
+
rescue IOError, EOFError => e
|
138
|
+
error = e
|
139
|
+
last_pos = @body.to_s.size
|
140
|
+
|
141
|
+
@io.read_all
|
142
|
+
@body = headless? ? @raw : @raw.split("\r\n\r\n", 2)[1]
|
143
|
+
@body = unzip @body, true if gzip?
|
144
|
+
end
|
145
|
+
|
146
|
+
@body = Zlib::Inflate.new(-Zlib::MAX_WBITS).inflate(@body) if deflated?
|
147
|
+
|
148
|
+
try_force_encoding @raw unless gzip? || deflated?
|
149
|
+
try_force_encoding @body
|
150
|
+
|
151
|
+
@read = true
|
152
|
+
|
153
|
+
yield @body[last_pos..-1] if block_given? && (deflated? || error)
|
154
|
+
|
155
|
+
@body
|
92
156
|
end
|
93
157
|
|
94
158
|
|
@@ -97,16 +161,72 @@ class Kronk
|
|
97
161
|
# including headers.
|
98
162
|
|
99
163
|
def byterate
|
100
|
-
return 0 unless
|
164
|
+
return 0 unless raw && @time.to_f > 0
|
101
165
|
@byterate = self.total_bytes / @time.to_f
|
102
166
|
end
|
103
167
|
|
104
168
|
|
105
169
|
##
|
106
|
-
# Size of the body in bytes.
|
170
|
+
# Size of the raw body in bytes.
|
107
171
|
|
108
172
|
def bytes
|
109
|
-
(headers["content-length"] ||
|
173
|
+
(headers["content-length"] || self.raw_body.bytes.count).to_i
|
174
|
+
end
|
175
|
+
|
176
|
+
|
177
|
+
##
|
178
|
+
# Is this a chunked streaming response?
|
179
|
+
|
180
|
+
def chunked?
|
181
|
+
return false unless @headers['transfer-encoding']
|
182
|
+
field = @headers['transfer-encoding']
|
183
|
+
(/(?:\A|[^\-\w])chunked(?![\-\w])/i =~ field) ? true : false
|
184
|
+
end
|
185
|
+
|
186
|
+
|
187
|
+
##
|
188
|
+
# Get the content length header.
|
189
|
+
|
190
|
+
def content_length
|
191
|
+
return nil unless @headers.has_key?('content-length')
|
192
|
+
len = @headers['content-length'].slice(/\d+/) or
|
193
|
+
raise HTTPHeaderSyntaxError, 'wrong Content-Length format'
|
194
|
+
len.to_i
|
195
|
+
end
|
196
|
+
|
197
|
+
|
198
|
+
##
|
199
|
+
# Assign the expected content length.
|
200
|
+
|
201
|
+
def content_length= len
|
202
|
+
unless len
|
203
|
+
@headers.delete 'content-length'
|
204
|
+
return nil
|
205
|
+
end
|
206
|
+
@headers['content-length'] = len.to_i.to_s
|
207
|
+
end
|
208
|
+
|
209
|
+
|
210
|
+
##
|
211
|
+
# Returns a Range object which represents the value of the Content-Range:
|
212
|
+
# header field.
|
213
|
+
# For a partial entity body, this indicates where this fragment
|
214
|
+
# fits inside the full entity body, as range of byte offsets.
|
215
|
+
|
216
|
+
def content_range
|
217
|
+
return nil unless @headers['content-range']
|
218
|
+
m = %r<bytes\s+(\d+)-(\d+)/(\d+|\*)>i.match(@headers['content-range']) or
|
219
|
+
raise HTTPHeaderSyntaxError, 'wrong Content-Range format'
|
220
|
+
m[1].to_i .. m[2].to_i
|
221
|
+
end
|
222
|
+
|
223
|
+
|
224
|
+
##
|
225
|
+
# The length of the range represented in Content-Range: header.
|
226
|
+
|
227
|
+
def range_length
|
228
|
+
r = content_range() or return nil
|
229
|
+
r.end - r.begin + 1
|
110
230
|
end
|
111
231
|
|
112
232
|
|
@@ -114,7 +234,25 @@ class Kronk
|
|
114
234
|
# Cookie header accessor.
|
115
235
|
|
116
236
|
def cookie
|
117
|
-
|
237
|
+
headers['cookie']
|
238
|
+
end
|
239
|
+
|
240
|
+
|
241
|
+
##
|
242
|
+
# Check if content encoding is deflated.
|
243
|
+
|
244
|
+
def deflated?
|
245
|
+
return !gzip? && @use_inflate unless @use_inflate.nil?
|
246
|
+
@use_inflate = headers["content-encoding"] == "deflate" if
|
247
|
+
headers["content-encoding"]
|
248
|
+
end
|
249
|
+
|
250
|
+
|
251
|
+
##
|
252
|
+
# Force the use of inflate.
|
253
|
+
|
254
|
+
def inflate= value
|
255
|
+
@use_inflate = value
|
118
256
|
end
|
119
257
|
|
120
258
|
|
@@ -139,31 +277,29 @@ class Kronk
|
|
139
277
|
def force_encoding new_encoding
|
140
278
|
new_encoding = Encoding.find new_encoding unless Encoding === new_encoding
|
141
279
|
@encoding = new_encoding
|
142
|
-
try_force_encoding
|
280
|
+
try_force_encoding self.body
|
143
281
|
try_force_encoding @raw
|
144
282
|
@encoding
|
145
283
|
end
|
146
284
|
|
147
285
|
|
148
|
-
##
|
149
|
-
# Accessor for downcased headers.
|
150
|
-
|
151
|
-
def headers
|
152
|
-
return @headers if @headers
|
153
|
-
@headers = @_res.to_hash.dup
|
154
|
-
@headers.keys.each{|h| @headers[h] = @headers[h].join(", ")}
|
155
|
-
@headers
|
156
|
-
end
|
157
|
-
|
158
286
|
alias to_hash headers
|
159
287
|
|
160
288
|
|
161
289
|
##
|
162
290
|
# If there was an error parsing the input as a standard http response,
|
163
|
-
# the input is assumed to be a body
|
291
|
+
# the input is assumed to be a body.
|
164
292
|
|
165
293
|
def headless?
|
166
|
-
|
294
|
+
@headless
|
295
|
+
end
|
296
|
+
|
297
|
+
|
298
|
+
##
|
299
|
+
# The version of the HTTP protocol returned.
|
300
|
+
|
301
|
+
def http_version
|
302
|
+
@http_version
|
167
303
|
end
|
168
304
|
|
169
305
|
|
@@ -171,9 +307,32 @@ class Kronk
|
|
171
307
|
# Ruby inspect.
|
172
308
|
|
173
309
|
def inspect
|
174
|
-
|
310
|
+
content_type = headers['content-type'] || "text/html"
|
311
|
+
"#<#{self.class}:#{@code} #{content_type} #{total_bytes}bytes>"
|
312
|
+
end
|
313
|
+
|
314
|
+
|
315
|
+
##
|
316
|
+
# Check if connection should be closed or not.
|
317
|
+
|
318
|
+
def close?
|
319
|
+
@headers['connection'].to_s.include?('close') ||
|
320
|
+
@headers['proxy-connection'].to_s.include?('close')
|
175
321
|
end
|
176
322
|
|
323
|
+
alias connection_close? close?
|
324
|
+
|
325
|
+
|
326
|
+
##
|
327
|
+
# Check if connection should stay alive.
|
328
|
+
|
329
|
+
def keep_alive?
|
330
|
+
@headers['connection'].to_s.include?('keep-alive') ||
|
331
|
+
@headers['proxy-connection'].to_s.include?('keep-alive')
|
332
|
+
end
|
333
|
+
|
334
|
+
alias connection_keep_alive? keep_alive?
|
335
|
+
|
177
336
|
|
178
337
|
##
|
179
338
|
# Returns the body data parsed according to the content type.
|
@@ -181,6 +340,7 @@ class Kronk
|
|
181
340
|
# the Content-Type, or will return the cached parsed body if available.
|
182
341
|
|
183
342
|
def parsed_body new_parser=nil
|
343
|
+
return unless body
|
184
344
|
@parsed_body ||= nil
|
185
345
|
|
186
346
|
return @parsed_body if @parsed_body && !new_parser
|
@@ -195,16 +355,16 @@ class Kronk
|
|
195
355
|
end if String === new_parser
|
196
356
|
|
197
357
|
raise MissingParser,
|
198
|
-
"No parser for
|
358
|
+
"No parser for: #{@headers['content-type']}" unless new_parser
|
199
359
|
|
200
360
|
begin
|
201
361
|
@parsed_body = new_parser.parse(self.body) or raise RuntimeError
|
202
362
|
|
203
|
-
rescue
|
363
|
+
rescue => e
|
204
364
|
msg = ParserError === e ?
|
205
365
|
e.message : "#{new_parser} failed parsing body"
|
206
366
|
|
207
|
-
msg << " returned by #{
|
367
|
+
msg << " returned by #{uri}" if uri
|
208
368
|
raise ParserError, msg
|
209
369
|
end
|
210
370
|
end
|
@@ -215,8 +375,10 @@ class Kronk
|
|
215
375
|
|
216
376
|
def parsed_header include_headers=true
|
217
377
|
out_headers = headers.dup
|
218
|
-
out_headers['status']
|
219
|
-
out_headers['http-version']
|
378
|
+
out_headers['status'] = @code
|
379
|
+
out_headers['http-version'] = http_version
|
380
|
+
out_headers['set-cookie'] &&= @cookies.select{|c| c['version'].nil? }
|
381
|
+
out_headers['set-cookie2'] &&= @cookies.select{|c| c['version'] == 1 }
|
220
382
|
|
221
383
|
case include_headers
|
222
384
|
when nil, false
|
@@ -256,18 +418,34 @@ class Kronk
|
|
256
418
|
end
|
257
419
|
|
258
420
|
|
421
|
+
##
|
422
|
+
# Returns the full raw HTTP response string after the full response
|
423
|
+
# has been read.
|
424
|
+
|
425
|
+
def raw
|
426
|
+
body
|
427
|
+
@raw
|
428
|
+
end
|
429
|
+
|
430
|
+
|
431
|
+
##
|
432
|
+
# Returns the body portion of the raw http response.
|
433
|
+
|
434
|
+
def raw_body
|
435
|
+
headless? ? raw : raw.split("\r\n\r\n", 2)[1]
|
436
|
+
end
|
437
|
+
|
438
|
+
|
259
439
|
##
|
260
440
|
# Returns the header portion of the raw http response.
|
261
441
|
|
262
|
-
def raw_header
|
442
|
+
def raw_header show=true
|
443
|
+
return if !show || headless?
|
263
444
|
headers = "#{@raw.split("\r\n\r\n", 2)[0]}\r\n"
|
264
445
|
|
265
|
-
case
|
266
|
-
when nil, false
|
267
|
-
nil
|
268
|
-
|
446
|
+
case show
|
269
447
|
when Array, String
|
270
|
-
includes = [*
|
448
|
+
includes = [*show].join("|")
|
271
449
|
headers.scan(%r{^((?:#{includes}): [^\n]*\n)}im).flatten.join
|
272
450
|
|
273
451
|
when true
|
@@ -276,13 +454,30 @@ class Kronk
|
|
276
454
|
end
|
277
455
|
|
278
456
|
|
457
|
+
##
|
458
|
+
# Maximum time to wait on IO.
|
459
|
+
|
460
|
+
def read_timeout
|
461
|
+
@io.read_timeout
|
462
|
+
end
|
463
|
+
|
464
|
+
|
465
|
+
##
|
466
|
+
# Assign maximum time to wait for IO data.
|
467
|
+
|
468
|
+
def read_timeout= val
|
469
|
+
@io.read_timeout = val
|
470
|
+
end
|
471
|
+
|
472
|
+
|
279
473
|
##
|
280
474
|
# Returns the location to redirect to. Prepends request url if location
|
281
475
|
# header is relative.
|
282
476
|
|
283
477
|
def location
|
284
|
-
return @
|
285
|
-
|
478
|
+
return unless @headers['location']
|
479
|
+
return @headers['location'] if !@request || !@request.uri
|
480
|
+
@request.uri.merge @headers['location']
|
286
481
|
end
|
287
482
|
|
288
483
|
|
@@ -297,28 +492,39 @@ class Kronk
|
|
297
492
|
##
|
298
493
|
# Follow the redirect and return a new Response instance.
|
299
494
|
# Returns nil if not redirect-able.
|
495
|
+
# Supports all Request#new options, plus:
|
496
|
+
# :trust_location:: Forwards HTTP auth to different host when true.
|
300
497
|
|
301
|
-
def follow_redirect opts={}
|
498
|
+
def follow_redirect opts={}, &block
|
302
499
|
return if !redirect?
|
303
|
-
|
500
|
+
new_opts = @request ? @request.to_hash : {}
|
501
|
+
new_opts[:http_method] = "GET" if @code == "303"
|
502
|
+
new_opts.merge!(opts)
|
503
|
+
new_opts.delete(:auth) if !opts[:trust_location] &&
|
504
|
+
(!@request || self.location.host != self.uri.host)
|
505
|
+
|
506
|
+
Request.new(self.location, new_opts).retrieve(new_opts, &block)
|
304
507
|
end
|
305
508
|
|
306
509
|
|
307
510
|
##
|
308
511
|
# Returns the raw response with selective headers and/or the body of
|
309
512
|
# the response. Supports the following options:
|
310
|
-
# :
|
311
|
-
# :
|
513
|
+
# :body:: Bool - Return the body; default true
|
514
|
+
# :headers:: Bool/String/Array - Return headers; default true
|
515
|
+
|
516
|
+
def to_s opts={}
|
517
|
+
return raw if opts[:raw] &&
|
518
|
+
(opts[:headers].nil? || opts[:headers] == true)
|
312
519
|
|
313
|
-
|
314
|
-
str = @body unless options[:no_body]
|
520
|
+
str = opts[:raw] ? self.raw_body : self.body unless opts[:body] == false
|
315
521
|
|
316
|
-
if
|
317
|
-
|
318
|
-
str
|
522
|
+
if opts[:headers] || opts[:headers].nil?
|
523
|
+
hstr = raw_header(opts[:headers] || true)
|
524
|
+
str = [hstr, str].compact.join "\r\n"
|
319
525
|
end
|
320
526
|
|
321
|
-
str
|
527
|
+
str.to_s
|
322
528
|
end
|
323
529
|
|
324
530
|
|
@@ -335,32 +541,32 @@ class Kronk
|
|
335
541
|
# :only_data:: String/Array - Extracts the data from given data paths
|
336
542
|
#
|
337
543
|
# Example:
|
338
|
-
# response.
|
339
|
-
# response.
|
544
|
+
# response.data :transform => [:delete, ["foo/0", "bar/1"]]
|
545
|
+
# response.data do |trans|
|
340
546
|
# trans.delete "foo/0", "bar/1"
|
341
547
|
# end
|
342
548
|
#
|
343
549
|
# See Kronk::Path::Transaction for supported transform actions.
|
344
550
|
|
345
|
-
def
|
551
|
+
def data opts={}
|
346
552
|
data = nil
|
347
553
|
|
348
|
-
unless
|
349
|
-
data = parsed_body
|
554
|
+
unless opts[:no_body]
|
555
|
+
data = parsed_body opts[:parser]
|
350
556
|
end
|
351
557
|
|
352
|
-
if
|
353
|
-
header_data = parsed_header(
|
558
|
+
if opts[:show_headers]
|
559
|
+
header_data = parsed_header(opts[:show_headers])
|
354
560
|
data &&= [header_data, data]
|
355
561
|
data ||= header_data
|
356
562
|
end
|
357
563
|
|
358
|
-
Path::Transaction.run data,
|
564
|
+
Path::Transaction.run data, opts do |t|
|
359
565
|
# Backward compatibility support
|
360
|
-
t.select(*
|
361
|
-
t.delete(*
|
566
|
+
t.select(*opts[:only_data]) if opts[:only_data]
|
567
|
+
t.delete(*opts[:ignore_data]) if opts[:ignore_data]
|
362
568
|
|
363
|
-
t.actions.concat
|
569
|
+
t.actions.concat opts[:transform] if opts[:transform]
|
364
570
|
|
365
571
|
yield t if block_given?
|
366
572
|
end
|
@@ -380,45 +586,52 @@ class Kronk
|
|
380
586
|
# :show_headers:: Boolean/String/Array - defines which headers to include
|
381
587
|
#
|
382
588
|
# If block is given, yields a Kronk::Path::Transaction instance to make
|
383
|
-
# transformations on the data. See Kronk::Response#
|
589
|
+
# transformations on the data. See Kronk::Response#data
|
384
590
|
|
385
|
-
def stringify
|
386
|
-
|
591
|
+
def stringify opts={}, &block
|
592
|
+
opts = merge_stringify_opts opts
|
593
|
+
|
594
|
+
if !opts[:raw] && (opts[:parser] || parser || opts[:no_body])
|
595
|
+
data = self.data opts, &block
|
596
|
+
DataString.new data, opts
|
387
597
|
|
388
|
-
if !options[:raw] && (options[:parser] || parser || options[:no_body])
|
389
|
-
data = selective_data options, &block
|
390
|
-
DataString.new data, options
|
391
598
|
else
|
392
|
-
|
599
|
+
self.to_s :body => !opts[:no_body],
|
600
|
+
:headers => (opts[:show_headers] || false),
|
601
|
+
:raw => opts[:raw]
|
393
602
|
end
|
394
603
|
|
395
604
|
rescue MissingParser
|
396
|
-
Cmd.verbose "Warning: No parser for #{@
|
397
|
-
|
605
|
+
Cmd.verbose "Warning: No parser for #{@headers['content-type']} [#{uri}]"
|
606
|
+
self.to_s :body => !opts[:no_body],
|
607
|
+
:headers => (opts[:show_headers] || false),
|
608
|
+
:raw => opts[:raw]
|
398
609
|
end
|
399
610
|
|
400
611
|
|
401
|
-
def merge_stringify_opts
|
402
|
-
|
612
|
+
def merge_stringify_opts opts # :nodoc:
|
613
|
+
return @stringify_opts if opts.empty?
|
614
|
+
|
615
|
+
opts = opts.dup
|
403
616
|
@stringify_opts.each do |key, val|
|
404
617
|
case key
|
405
618
|
# Response headers - Boolean, String, or Array
|
406
619
|
when :show_headers
|
407
|
-
next if
|
408
|
-
(
|
620
|
+
next if opts.has_key?(key) &&
|
621
|
+
(opts[key].class != Array || val == true || val == false)
|
409
622
|
|
410
|
-
|
411
|
-
[*
|
623
|
+
opts[key] = (val == true || val == false) ? val :
|
624
|
+
[*opts[key]] | [*val]
|
412
625
|
|
413
626
|
# String or Array
|
414
627
|
when :only_data, :ignore_data
|
415
|
-
|
628
|
+
opts[key] = [*opts[key]] | [*val]
|
416
629
|
|
417
630
|
else
|
418
|
-
|
631
|
+
opts[key] = val if opts[key].nil?
|
419
632
|
end
|
420
633
|
end
|
421
|
-
|
634
|
+
opts
|
422
635
|
end
|
423
636
|
|
424
637
|
|
@@ -430,144 +643,249 @@ class Kronk
|
|
430
643
|
end
|
431
644
|
|
432
645
|
|
646
|
+
##
|
647
|
+
# Check if the Response body has been read.
|
648
|
+
|
649
|
+
def read?
|
650
|
+
@read
|
651
|
+
end
|
652
|
+
|
653
|
+
|
433
654
|
##
|
434
655
|
# Number of bytes of the response including the header.
|
435
656
|
|
436
657
|
def total_bytes
|
437
|
-
|
658
|
+
return raw.bytes.count if @read
|
659
|
+
return raw_header.bytes.count unless body_permitted?
|
660
|
+
raw_header.bytes.count + (content_length || range_length).to_i + 2
|
438
661
|
end
|
439
662
|
|
440
663
|
|
441
|
-
|
664
|
+
##
|
665
|
+
# The URI of the request if or the file read if available.
|
666
|
+
|
667
|
+
def uri
|
668
|
+
@request && @request.uri || File === @io.io && URI.parse(@io.io.path)
|
669
|
+
end
|
442
670
|
|
443
671
|
|
444
672
|
##
|
445
|
-
#
|
673
|
+
# Require the use of gzip for reading the body.
|
446
674
|
|
447
|
-
def
|
448
|
-
|
449
|
-
|
450
|
-
if Kronk::Cmd.windows? && File === resp_io
|
451
|
-
resp_io = WinFileIO.new resp_io.path, io.read
|
452
|
-
end
|
675
|
+
def gzip= value
|
676
|
+
@use_gzip = value
|
677
|
+
end
|
453
678
|
|
454
|
-
io = Net::BufferedIO === resp_io ? resp_io : Net::BufferedIO.new(resp_io)
|
455
|
-
io.debug_output = debug_io = StringIO.new
|
456
679
|
|
457
|
-
|
458
|
-
|
459
|
-
resp.reading_body io, true do;end
|
460
|
-
|
461
|
-
rescue Net::HTTPBadResponse
|
462
|
-
ext = "text/html"
|
463
|
-
ext = File.extname(resp_io.path)[1..-1] if
|
464
|
-
WinFileIO === resp_io || File === resp_io
|
465
|
-
|
466
|
-
resp_io.rewind
|
467
|
-
resp = HeadlessResponse.new resp_io.read, ext
|
468
|
-
|
469
|
-
rescue EOFError
|
470
|
-
# If no response was read because it's too short
|
471
|
-
unless resp
|
472
|
-
resp_io.rewind
|
473
|
-
resp = HeadlessResponse.new resp_io.read, "html"
|
474
|
-
end
|
475
|
-
end
|
680
|
+
##
|
681
|
+
# Check if gzip should be used.
|
476
682
|
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
683
|
+
def gzip?
|
684
|
+
return @use_gzip unless @use_gzip.nil?
|
685
|
+
@use_gzip = headers["content-encoding"] == "gzip" if
|
686
|
+
headers["content-encoding"]
|
687
|
+
end
|
688
|
+
|
689
|
+
|
690
|
+
private
|
691
|
+
|
692
|
+
|
693
|
+
##
|
694
|
+
# Check if the response should have a body or not.
|
695
|
+
|
696
|
+
def body_permitted?
|
697
|
+
Net::HTTPResponse::CODE_TO_OBJ[@code].const_get(:HAS_BODY) rescue true
|
698
|
+
end
|
699
|
+
|
700
|
+
|
701
|
+
##
|
702
|
+
# Check if a headless response is allowable based on the IO given
|
703
|
+
# to the constructor.
|
481
704
|
|
482
|
-
|
705
|
+
def headless_ok? io
|
706
|
+
File === io || String === io || StringIO === io
|
483
707
|
end
|
484
708
|
|
485
709
|
|
486
710
|
##
|
487
|
-
#
|
488
|
-
# containing the raw request, response, and number of bytes received.
|
711
|
+
# Get response status and headers from BufferedIO instance.
|
489
712
|
|
490
|
-
def
|
491
|
-
|
492
|
-
|
493
|
-
|
713
|
+
def response_from_io buff_io, allow_headless=false
|
714
|
+
begin
|
715
|
+
@http_version, @code, @msg = read_status_line buff_io
|
716
|
+
@headers = read_headers buff_io
|
494
717
|
|
495
|
-
|
496
|
-
|
718
|
+
rescue EOFError, Kronk::HTTPBadResponse
|
719
|
+
raise unless allow_headless
|
720
|
+
@http_version, @code, @msg = ["1.0", "200", "OK"]
|
497
721
|
|
498
|
-
|
499
|
-
|
500
|
-
output.delete_at 0
|
501
|
-
end
|
722
|
+
ext = File === buff_io.io ?
|
723
|
+
File.extname(buff_io.io.path)[1..-1] : "html"
|
502
724
|
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
725
|
+
encoding = buff_io.io.respond_to?(:external_encoding) ?
|
726
|
+
buff_io.io.external_encoding : "UTF-8"
|
727
|
+
@headers = {
|
728
|
+
'content-type' => "text/#{ext}; charset=#{encoding}",
|
729
|
+
}
|
507
730
|
|
508
|
-
|
509
|
-
next unless line[0..2] == "-> "
|
510
|
-
resp << instance_eval(line[2..-1])
|
731
|
+
@headless = true
|
511
732
|
end
|
512
733
|
|
513
|
-
|
734
|
+
@read = true unless body_permitted?
|
514
735
|
end
|
515
736
|
|
516
737
|
|
517
738
|
##
|
518
|
-
#
|
519
|
-
# it responds to 'force_encoding'.
|
520
|
-
# Returns the string given with the new encoding.
|
739
|
+
# Read the body from IO.
|
521
740
|
|
522
|
-
def
|
523
|
-
|
524
|
-
|
741
|
+
def read_body target=nil
|
742
|
+
block = lambda do |str|
|
743
|
+
if block_given?
|
744
|
+
yield str
|
745
|
+
else
|
746
|
+
target << str
|
747
|
+
end
|
748
|
+
end
|
749
|
+
|
750
|
+
dest = Net::ReadAdapter.new block
|
751
|
+
|
752
|
+
if chunked?
|
753
|
+
read_chunked dest
|
754
|
+
return
|
755
|
+
end
|
756
|
+
clen = content_length()
|
757
|
+
if clen
|
758
|
+
@io.read clen, dest, true # ignore EOF
|
759
|
+
return
|
760
|
+
end
|
761
|
+
clen = range_length()
|
762
|
+
if clen
|
763
|
+
@io.read clen, dest
|
764
|
+
return
|
765
|
+
end
|
766
|
+
@io.read_all dest
|
525
767
|
end
|
526
|
-
end
|
527
768
|
|
528
769
|
|
529
|
-
|
530
|
-
|
770
|
+
##
|
771
|
+
# Unzip a chunk of the body being read.
|
772
|
+
|
773
|
+
def unzip str, force_new=false
|
774
|
+
return str if str.empty?
|
531
775
|
|
532
|
-
|
776
|
+
@gzip_io = StringIO.new if !@gzip_io || force_new
|
777
|
+
pos = @gzip_io.pos
|
778
|
+
@gzip_io << str
|
779
|
+
@gzip_io.pos = pos
|
533
780
|
|
534
|
-
|
781
|
+
@gzip = Zlib::GzipReader.new @gzip_io if !@gzip || force_new
|
535
782
|
|
536
|
-
|
537
|
-
|
538
|
-
@raw = body
|
539
|
-
@code = "200"
|
783
|
+
@gzip.read rescue ""
|
784
|
+
end
|
540
785
|
|
541
|
-
encoding = body.respond_to?(:encoding) ? body.encoding : "UTF-8"
|
542
786
|
|
543
|
-
|
544
|
-
|
545
|
-
|
787
|
+
##
|
788
|
+
# Read a chunked response body.
|
789
|
+
|
790
|
+
def read_chunked dest
|
791
|
+
len = nil
|
792
|
+
total = 0
|
793
|
+
while true
|
794
|
+
line = @io.readline
|
795
|
+
hexlen = line.slice(/[0-9a-fA-F]+/) or
|
796
|
+
raise Kronk::HTTPBadResponse, "wrong chunk size line: #{line}"
|
797
|
+
len = hexlen.hex
|
798
|
+
break if len == 0
|
799
|
+
begin
|
800
|
+
@io.read len, dest
|
801
|
+
ensure
|
802
|
+
total += len
|
803
|
+
@io.read 2 # \r\n
|
804
|
+
end
|
805
|
+
end
|
806
|
+
until @io.readline.empty?
|
807
|
+
# none
|
808
|
+
end
|
546
809
|
end
|
547
810
|
|
548
811
|
|
549
812
|
##
|
550
|
-
#
|
813
|
+
# Read the first line of the response. (Stolen from Net::HTTP)
|
551
814
|
|
552
|
-
def
|
553
|
-
|
815
|
+
def read_status_line sock
|
816
|
+
str = sock.readline until str && !str.empty?
|
817
|
+
m = /\AHTTP(?:\/(\d+\.\d+))?\s+(\d\d\d)\s*(.*)\z/in.match(str) or
|
818
|
+
raise Kronk::HTTPBadResponse, "wrong status line: #{str.dump}"
|
819
|
+
m.captures
|
554
820
|
end
|
555
821
|
|
556
|
-
|
557
|
-
|
822
|
+
|
823
|
+
##
|
824
|
+
# Read response headers. (Stolen from Net::HTTP)
|
825
|
+
|
826
|
+
def read_headers sock
|
827
|
+
res_headers = {}
|
828
|
+
key = value = nil
|
829
|
+
while true
|
830
|
+
line = sock.readuntil("\n", true).sub(/\s+\z/, '')
|
831
|
+
break if line.empty?
|
832
|
+
if line[0] == ?\s or line[0] == ?\t and value
|
833
|
+
value << ' ' unless value.empty?
|
834
|
+
value << line.strip
|
835
|
+
else
|
836
|
+
assign_header(res_headers, key, value) if key
|
837
|
+
key, value = line.strip.split(/\s*:\s*/, 2)
|
838
|
+
key = key.downcase
|
839
|
+
raise Kronk::HTTPBadResponse, 'wrong header line format' if value.nil?
|
840
|
+
end
|
841
|
+
end
|
842
|
+
assign_header(res_headers, key, value) if key
|
843
|
+
res_headers
|
558
844
|
end
|
559
845
|
|
560
846
|
|
561
|
-
|
562
|
-
|
847
|
+
def assign_header res_headers, key, value
|
848
|
+
res_headers[key] = Array(res_headers[key]) if res_headers[key]
|
849
|
+
Array === res_headers[key] ?
|
850
|
+
res_headers[key] << value :
|
851
|
+
res_headers[key] = value
|
852
|
+
end
|
563
853
|
|
564
|
-
def to_hash
|
565
|
-
head_out = @header.dup
|
566
|
-
head_out.keys.each do |key|
|
567
|
-
head_out[key.downcase] = [head_out.delete(key)]
|
568
|
-
end
|
569
854
|
|
570
|
-
|
855
|
+
##
|
856
|
+
# Assigns self.encoding to the passed string if
|
857
|
+
# it responds to 'force_encoding'.
|
858
|
+
# Returns the string given with the new encoding.
|
859
|
+
|
860
|
+
def try_force_encoding str
|
861
|
+
str.force_encoding encoding if str.respond_to? :force_encoding
|
862
|
+
str
|
571
863
|
end
|
572
864
|
end
|
573
865
|
end
|
866
|
+
|
867
|
+
|
868
|
+
class CookieJar::Cookie
|
869
|
+
def to_hash
|
870
|
+
result = {
|
871
|
+
'name' => @name,
|
872
|
+
'value' => @value,
|
873
|
+
'domain' => @domain,
|
874
|
+
'path' => @path,
|
875
|
+
}
|
876
|
+
{
|
877
|
+
'expiry' => @expiry,
|
878
|
+
'secure' => (true if @secure),
|
879
|
+
'http_only' => (true if @http_only),
|
880
|
+
'version' => (@version if version != 0),
|
881
|
+
'comment' => @comment,
|
882
|
+
'comment_url' => @comment_url,
|
883
|
+
'discard' => (true if @discard),
|
884
|
+
'ports' => @ports
|
885
|
+
}.each do |name, value|
|
886
|
+
result[name] = value if value
|
887
|
+
end
|
888
|
+
|
889
|
+
result
|
890
|
+
end
|
891
|
+
end
|