kronk 1.8.7 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/History.rdoc +22 -0
- data/Manifest.txt +6 -1
- data/README.rdoc +7 -2
- data/Rakefile +5 -4
- data/TODO.rdoc +3 -5
- data/lib/kronk.rb +20 -14
- data/lib/kronk/buffered_io.rb +7 -0
- data/lib/kronk/cmd.rb +25 -9
- data/lib/kronk/constants.rb +8 -0
- data/lib/kronk/http.rb +129 -2
- data/lib/kronk/multipart.rb +82 -0
- data/lib/kronk/multipart_io.rb +88 -0
- data/lib/kronk/player.rb +1 -1
- data/lib/kronk/player/benchmark.rb +69 -24
- data/lib/kronk/player/download.rb +87 -0
- data/lib/kronk/player/suite.rb +23 -11
- data/lib/kronk/request.rb +144 -77
- data/lib/kronk/response.rb +55 -18
- data/test/mocks/200_response.plist +1 -1
- data/test/test_cmd.rb +5 -3
- data/test/test_helper.rb +20 -10
- data/test/test_multipart.rb +144 -0
- data/test/test_multipart_io.rb +92 -0
- data/test/test_player.rb +7 -2
- data/test/test_request.rb +160 -43
- data/test/test_response.rb +34 -2
- metadata +27 -4
data/lib/kronk/response.rb
CHANGED
@@ -10,6 +10,7 @@ class Kronk
|
|
10
10
|
|
11
11
|
|
12
12
|
ENCODING_MATCHER = /(^|;\s?)charset=(.*?)\s*(;|$)/
|
13
|
+
DEFAULT_ENCODING = "ASCII-8BIT"
|
13
14
|
|
14
15
|
##
|
15
16
|
# Read http response from a file and return a Kronk::Response instance.
|
@@ -25,7 +26,7 @@ class Kronk
|
|
25
26
|
|
26
27
|
|
27
28
|
attr_reader :code, :io, :cookies, :headers
|
28
|
-
attr_accessor :read, :request, :stringify_opts, :time
|
29
|
+
attr_accessor :read, :request, :stringify_opts, :time, :conn_time
|
29
30
|
|
30
31
|
##
|
31
32
|
# Create a new Response object from a String or IO.
|
@@ -129,6 +130,7 @@ class Kronk
|
|
129
130
|
|
130
131
|
begin
|
131
132
|
read_body do |chunk|
|
133
|
+
chunk = chunk.dup
|
132
134
|
chunk = unzip chunk if gzip?
|
133
135
|
|
134
136
|
try_force_encoding chunk
|
@@ -169,10 +171,10 @@ class Kronk
|
|
169
171
|
|
170
172
|
|
171
173
|
##
|
172
|
-
#
|
174
|
+
# Current size of the http body in bytes.
|
173
175
|
|
174
176
|
def bytes
|
175
|
-
|
177
|
+
self.raw_body.bytes.count
|
176
178
|
end
|
177
179
|
|
178
180
|
|
@@ -232,6 +234,29 @@ class Kronk
|
|
232
234
|
end
|
233
235
|
|
234
236
|
|
237
|
+
##
|
238
|
+
# The extension or file type corresponding to the body,
|
239
|
+
# based on the Content-Type. Defaults to 'txt' if none can be determined
|
240
|
+
# or Content-Type is text/plain.
|
241
|
+
# application/json => 'json'
|
242
|
+
# text/html => 'html'
|
243
|
+
# application/foo+xml => 'xml'
|
244
|
+
|
245
|
+
def ext
|
246
|
+
file_ext =
|
247
|
+
if headers['content-type']
|
248
|
+
types = MIME::Types[headers['content-type'].sub(%r{/\w+\+}, '/')]
|
249
|
+
types[0].extensions[0] unless types.empty?
|
250
|
+
|
251
|
+
elsif uri
|
252
|
+
File.extname(uri.path)[1..-1]
|
253
|
+
end
|
254
|
+
|
255
|
+
file_ext = "txt" if !file_ext || file_ext.strip.empty?
|
256
|
+
file_ext
|
257
|
+
end
|
258
|
+
|
259
|
+
|
235
260
|
##
|
236
261
|
# Cookie header accessor.
|
237
262
|
|
@@ -264,10 +289,9 @@ class Kronk
|
|
264
289
|
|
265
290
|
def encoding
|
266
291
|
return @encoding if @encoding
|
267
|
-
|
268
|
-
c_type = headers["content-type"] =~ ENCODING_MATCHER
|
292
|
+
c_type = headers["content-type"].to_s =~ ENCODING_MATCHER
|
269
293
|
@encoding = $2 if c_type
|
270
|
-
@encoding ||=
|
294
|
+
@encoding ||= DEFAULT_ENCODING
|
271
295
|
@encoding = Encoding.find(@encoding) if defined?(Encoding)
|
272
296
|
@encoding
|
273
297
|
end
|
@@ -309,8 +333,9 @@ class Kronk
|
|
309
333
|
# Ruby inspect.
|
310
334
|
|
311
335
|
def inspect
|
312
|
-
content_type = headers['content-type'] || "text/
|
313
|
-
"#<#{self.class}:#{@code} #{content_type}
|
336
|
+
content_type = headers['content-type'] || "text/plain"
|
337
|
+
"#<#{self.class}:#{@code} #{content_type} \
|
338
|
+
#{total_bytes}/#{expected_bytes}bytes>"
|
314
339
|
end
|
315
340
|
|
316
341
|
|
@@ -329,8 +354,8 @@ class Kronk
|
|
329
354
|
# Check if connection should stay alive.
|
330
355
|
|
331
356
|
def keep_alive?
|
332
|
-
@headers['connection'].to_s.include?('
|
333
|
-
@headers['proxy-connection'].to_s.include?('
|
357
|
+
@headers['connection'].to_s.include?('Keep-Alive') ||
|
358
|
+
@headers['proxy-connection'].to_s.include?('Keep-Alive')
|
334
359
|
end
|
335
360
|
|
336
361
|
alias connection_keep_alive? keep_alive?
|
@@ -406,7 +431,7 @@ class Kronk
|
|
406
431
|
# The parser to use on the body.
|
407
432
|
|
408
433
|
def parser
|
409
|
-
@parser ||= Kronk.parser_for
|
434
|
+
@parser ||= Kronk.parser_for self.ext
|
410
435
|
end
|
411
436
|
|
412
437
|
|
@@ -434,7 +459,7 @@ class Kronk
|
|
434
459
|
# Returns the body portion of the raw http response.
|
435
460
|
|
436
461
|
def raw_body
|
437
|
-
headless? ? raw :
|
462
|
+
headless? ? @raw.to_s : @body.to_s
|
438
463
|
end
|
439
464
|
|
440
465
|
|
@@ -554,12 +579,13 @@ class Kronk
|
|
554
579
|
# :only_data:: String/Array - Extracts the data from given data paths
|
555
580
|
#
|
556
581
|
# Example:
|
557
|
-
# response.data :transform => [:delete, ["foo/0", "bar/1"]]
|
582
|
+
# response.data :transform => [[:delete, ["foo/0", "bar/1"]]]
|
558
583
|
# response.data do |trans|
|
559
584
|
# trans.delete "foo/0", "bar/1"
|
560
585
|
# end
|
561
586
|
#
|
562
|
-
# See Path::Transaction for supported transform actions
|
587
|
+
# See Path::Transaction for supported transform actions in the
|
588
|
+
# {ruby-path gem}[http://github.com/yaksnrainbows/ruby-path].
|
563
589
|
|
564
590
|
def data opts={}
|
565
591
|
data = nil
|
@@ -672,6 +698,16 @@ class Kronk
|
|
672
698
|
# Number of bytes of the response including the header.
|
673
699
|
|
674
700
|
def total_bytes
|
701
|
+
return raw.bytes.count if @read
|
702
|
+
return raw_header.bytes.count unless body_permitted?
|
703
|
+
raw_header.to_s.bytes.count + bytes + 2
|
704
|
+
end
|
705
|
+
|
706
|
+
|
707
|
+
##
|
708
|
+
# Expected number of bytes to read from the server, including the header.
|
709
|
+
|
710
|
+
def expected_bytes
|
675
711
|
return raw.bytes.count if @read
|
676
712
|
return raw_header.bytes.count unless body_permitted?
|
677
713
|
raw_header.to_s.bytes.count + (content_length || range_length).to_i + 2
|
@@ -738,13 +774,14 @@ class Kronk
|
|
738
774
|
|
739
775
|
buff_io.rewind
|
740
776
|
|
741
|
-
|
742
|
-
|
777
|
+
ctype = ["text/plain"]
|
778
|
+
ctype = MIME::Types.of(buff_io.io.path).concat ctype if
|
779
|
+
File === buff_io.io
|
743
780
|
|
744
781
|
encoding = buff_io.io.respond_to?(:external_encoding) ?
|
745
|
-
buff_io.io.external_encoding :
|
782
|
+
buff_io.io.external_encoding : DEFAULT_ENCODING
|
746
783
|
@headers = {
|
747
|
-
'content-type' => "
|
784
|
+
'content-type' => "#{ctype[0]}; charset=#{encoding}",
|
748
785
|
}
|
749
786
|
|
750
787
|
@headless = true
|
data/test/test_cmd.rb
CHANGED
@@ -321,11 +321,11 @@ class TestCmd < Test::Unit::TestCase
|
|
321
321
|
opts = Kronk::Cmd.parse_args %w{uri -p mock_file}
|
322
322
|
assert_equal mock_file, opts[:player].input.io
|
323
323
|
|
324
|
-
opts = Kronk::Cmd.parse_args %w{uri --benchmark mock_file}
|
324
|
+
opts = Kronk::Cmd.parse_args %w{uri --benchmark -p mock_file}
|
325
325
|
assert_equal mock_file, opts[:player].input.io
|
326
326
|
assert_equal Kronk::Player::Benchmark, opts[:player].class
|
327
327
|
|
328
|
-
opts = Kronk::Cmd.parse_args %w{uri --stream mock_file}
|
328
|
+
opts = Kronk::Cmd.parse_args %w{uri --stream -p mock_file}
|
329
329
|
assert_equal mock_file, opts[:player].input.io
|
330
330
|
assert_equal Kronk::Player::Stream, opts[:player].class
|
331
331
|
end
|
@@ -366,7 +366,8 @@ class TestCmd < Test::Unit::TestCase
|
|
366
366
|
|
367
367
|
def test_parse_args_http_options
|
368
368
|
opts = Kronk::Cmd.parse_args %w{uri -A foo -L --no-cookies -? bar
|
369
|
-
--suff /tail -X PUT -x example.com:2000 --form foo=bar
|
369
|
+
--suff /tail -X PUT -x example.com:2000 --form foo=bar
|
370
|
+
--form-upload file=test/mocks/200_response.json}
|
370
371
|
|
371
372
|
assert_equal "foo", opts[:user_agent]
|
372
373
|
assert_equal true, opts[:follow_redirects]
|
@@ -375,6 +376,7 @@ class TestCmd < Test::Unit::TestCase
|
|
375
376
|
assert_equal "/tail", opts[:uri_suffix]
|
376
377
|
assert_equal "PUT", opts[:http_method]
|
377
378
|
assert_equal "foo=bar", opts[:form]
|
379
|
+
assert_equal "file=test/mocks/200_response.json", opts[:form_upload]
|
378
380
|
assert_equal({:host => "example.com", :port => "2000"}, opts[:proxy])
|
379
381
|
|
380
382
|
opts = Kronk::Cmd.parse_args %w{uri -L 3}
|
data/test/test_helper.rb
CHANGED
@@ -160,33 +160,43 @@ ensure
|
|
160
160
|
end
|
161
161
|
|
162
162
|
|
163
|
-
def expect_request req_method, url,
|
163
|
+
def expect_request req_method, url, opts={}
|
164
164
|
uri = URI.parse url
|
165
165
|
|
166
|
-
resp = Kronk::Response.new(
|
167
|
-
resp.stubs(:code).returns(
|
166
|
+
resp = Kronk::Response.new(opts[:returns] || mock_200_response)
|
167
|
+
resp.stubs(:code).returns(opts[:status] || '200')
|
168
168
|
resp.stubs(:to_hash).returns Hash.new
|
169
169
|
|
170
170
|
http = mock 'http'
|
171
171
|
req = mock 'req'
|
172
172
|
|
173
|
-
data =
|
173
|
+
data = opts[:data]
|
174
174
|
data &&= Hash === data ? Kronk::Request.build_query(data) : data.to_s
|
175
175
|
|
176
|
-
|
176
|
+
req.stubs(:body).returns(data)
|
177
|
+
req.stubs(:body_stream).returns(StringIO.new(data.to_s))
|
178
|
+
|
179
|
+
req.stubs(:[]).with('Content-Length').returns(data)
|
180
|
+
req.stubs(:[]=)
|
181
|
+
|
182
|
+
headers = opts[:headers] || Hash.new
|
177
183
|
headers['User-Agent'] ||= Kronk::DEFAULT_USER_AGENT
|
184
|
+
headers['Connection'] ||= 'Keep-Alive'
|
178
185
|
|
179
|
-
|
186
|
+
http.expects(:started?).returns false
|
187
|
+
http.expects(:start)
|
180
188
|
req.expects(:body=).with(data)
|
181
189
|
|
182
190
|
Kronk::Request::VanillaRequest.expects(:new).
|
183
191
|
with(req_method.to_s.upcase, uri.request_uri, headers).returns req
|
184
192
|
|
185
|
-
|
193
|
+
proxy = opts[:proxy] || {}
|
194
|
+
|
195
|
+
Kronk::HTTP.expects(:new).
|
196
|
+
with(uri.host, uri.port, {:proxy => proxy, :ssl => !!opts[:ssl]}).
|
197
|
+
returns http
|
186
198
|
|
187
|
-
http.expects(:request).
|
188
|
-
with(req, nil, has_entry(:request => instance_of(Kronk::Request))).
|
189
|
-
returns resp
|
199
|
+
http.expects(:request).with(req, nil, {}).returns resp
|
190
200
|
|
191
201
|
yield http, req, resp if block_given?
|
192
202
|
|
@@ -0,0 +1,144 @@
|
|
1
|
+
require 'test/test_helper'
|
2
|
+
|
3
|
+
class TestMultipart < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def setup
|
6
|
+
@multi = Kronk::Multipart.new "foobar"
|
7
|
+
end
|
8
|
+
|
9
|
+
|
10
|
+
def test_initialize
|
11
|
+
assert_equal "foobar", @multi.boundary
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
def test_add
|
16
|
+
@multi.add "foo", "bar"
|
17
|
+
expected = [{'content-disposition' => 'form-data; name="foo"'}, "bar"]
|
18
|
+
assert_equal expected, @multi.parts.last
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
def test_add_headers
|
23
|
+
@multi.add "foo", "bar", "X-Header" => "blah"
|
24
|
+
expected = [{'content-disposition' => 'form-data; name="foo"',
|
25
|
+
"X-Header" => "blah"}, "bar"]
|
26
|
+
assert_equal expected, @multi.parts.last
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
def test_add_file
|
31
|
+
file = File.open("test/mocks/200_response.json", "rb")
|
32
|
+
@multi.add "foo", file
|
33
|
+
|
34
|
+
expected = [{
|
35
|
+
"content-disposition" =>
|
36
|
+
'form-data; name="foo"; filename="200_response.json"',
|
37
|
+
"Content-Type" => "application/json",
|
38
|
+
"Content-Transfer-Encoding" => "binary"
|
39
|
+
}, file]
|
40
|
+
|
41
|
+
assert_equal expected, @multi.parts.last
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
def test_add_io
|
46
|
+
prd, pwr = IO.pipe
|
47
|
+
@multi.add "foo", prd
|
48
|
+
|
49
|
+
expected = [{
|
50
|
+
"content-disposition" => 'form-data; name="foo"',
|
51
|
+
"Content-Type" => "application/octet-stream",
|
52
|
+
"Content-Transfer-Encoding" => "binary"
|
53
|
+
}, prd]
|
54
|
+
|
55
|
+
assert_equal expected, @multi.parts.last
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
def test_add_escaped_name
|
60
|
+
@multi.add "foo:10", "bar"
|
61
|
+
expected = [{'content-disposition' => 'form-data; name="foo:10"'}, "bar"]
|
62
|
+
assert_equal expected, @multi.parts.last
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
def test_to_io
|
67
|
+
@multi.add "key1", "bar"
|
68
|
+
@multi.add "key2", "some value"
|
69
|
+
@multi.add "key3", "other thing"
|
70
|
+
|
71
|
+
io = @multi.to_io
|
72
|
+
assert_equal Kronk::MultipartIO, io.class
|
73
|
+
assert_equal 1, io.parts.length
|
74
|
+
assert_equal StringIO, io.parts.first.class
|
75
|
+
|
76
|
+
expected = <<-STR
|
77
|
+
--foobar\r
|
78
|
+
content-disposition: form-data; name="key1"\r
|
79
|
+
\r
|
80
|
+
bar\r
|
81
|
+
--foobar\r
|
82
|
+
content-disposition: form-data; name="key2"\r
|
83
|
+
\r
|
84
|
+
some value\r
|
85
|
+
--foobar\r
|
86
|
+
content-disposition: form-data; name="key3"\r
|
87
|
+
\r
|
88
|
+
other thing\r
|
89
|
+
--foobar--
|
90
|
+
STR
|
91
|
+
assert_equal expected.strip, io.read
|
92
|
+
end
|
93
|
+
|
94
|
+
|
95
|
+
def test_to_io_with_file
|
96
|
+
file = File.open("test/mocks/200_response.json", "rb")
|
97
|
+
@multi.add "key1", "bar"
|
98
|
+
@multi.add "key2", "some value"
|
99
|
+
@multi.add "my_file", file
|
100
|
+
|
101
|
+
io = @multi.to_io
|
102
|
+
assert_equal 3, io.parts.length
|
103
|
+
assert_equal StringIO, io.parts.first.class
|
104
|
+
assert_equal file, io.parts[1]
|
105
|
+
assert_equal StringIO, io.parts.last.class
|
106
|
+
|
107
|
+
file_content = File.read("test/mocks/200_response.json")
|
108
|
+
|
109
|
+
head = <<-STR
|
110
|
+
--foobar\r
|
111
|
+
content-disposition: form-data; name="key1"\r
|
112
|
+
\r
|
113
|
+
bar\r
|
114
|
+
--foobar\r
|
115
|
+
content-disposition: form-data; name="key2"\r
|
116
|
+
\r
|
117
|
+
some value\r
|
118
|
+
--foobar\r
|
119
|
+
content-disposition: form-data; name="my_file"; filename="200_response.json"\r
|
120
|
+
STR
|
121
|
+
|
122
|
+
ctype_line = "Content-Type: application/json\r\n"
|
123
|
+
ctran_line = "Content-Transfer-Encoding: binary\r\n"
|
124
|
+
tail = "\r\n#{file_content}\r\n--foobar--"
|
125
|
+
|
126
|
+
output = io.read head.bytes.count
|
127
|
+
assert_equal head, output
|
128
|
+
|
129
|
+
output = ""
|
130
|
+
output << io.read(1) while output[-1..-1] != "\n"
|
131
|
+
|
132
|
+
comp1, comp2 = output.length == ctype_line.length ?
|
133
|
+
[ctype_line, ctran_line] : [ctran_line, ctype_line]
|
134
|
+
|
135
|
+
assert_equal comp1, output
|
136
|
+
|
137
|
+
output = ""
|
138
|
+
output << io.read(1) while output[-1..-1] != "\n"
|
139
|
+
|
140
|
+
assert_equal comp2, output
|
141
|
+
|
142
|
+
assert_equal tail, io.read
|
143
|
+
end
|
144
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'test/test_helper'
|
2
|
+
|
3
|
+
class TestMultipartIo < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def setup
|
6
|
+
strio = StringIO.new "thing"
|
7
|
+
@io = Kronk::MultipartIO.new "foo", "bar", strio
|
8
|
+
|
9
|
+
@pipe_rd, @pipe_wr = IO.pipe
|
10
|
+
@pipe_wr.write "thing"
|
11
|
+
@pipe_wr.close
|
12
|
+
@pio = Kronk::MultipartIO.new "foo", @pipe_rd, "bar"
|
13
|
+
|
14
|
+
@file = File.open 'test/mocks/200_response.json', "r"
|
15
|
+
@fio = Kronk::MultipartIO.new "foo", @file, "bar"
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
def teardown
|
20
|
+
@file.close
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
def test_initialize
|
25
|
+
assert_equal StringIO, @io.parts[0].class
|
26
|
+
assert_equal "foo", @io.parts[0].read
|
27
|
+
assert_equal StringIO, @io.parts[1].class
|
28
|
+
assert_equal "bar", @io.parts[1].read
|
29
|
+
assert_equal StringIO, @io.parts[2].class
|
30
|
+
assert_equal "thing", @io.parts[2].read
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
def test_size
|
35
|
+
assert_equal 11, @io.size
|
36
|
+
assert_nil @pio.size
|
37
|
+
assert_equal((@file.size + 6), @fio.size)
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
def test_read_all_strings
|
42
|
+
assert_equal "foobarthing", @io.read_all
|
43
|
+
assert_nil @io.read 1
|
44
|
+
assert_equal "", @io.read
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
def test_read_all_file
|
49
|
+
assert_equal "foo#{File.read "test/mocks/200_response.json"}bar",
|
50
|
+
@fio.read_all
|
51
|
+
assert_nil @fio.read 1
|
52
|
+
assert_equal "", @fio.read
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
def test_read_all_io
|
57
|
+
assert_equal "foothingbar",
|
58
|
+
@pio.read_all
|
59
|
+
assert_nil @pio.read 1
|
60
|
+
assert_equal "", @pio.read
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
def test_read_all_after_read_partial
|
65
|
+
assert_equal "foo", @pio.read(3)
|
66
|
+
assert_equal "thingbar", @pio.read
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
def test_eof
|
71
|
+
assert !@pio.eof?, "EOF should NOT be reached"
|
72
|
+
@pio.read 5
|
73
|
+
|
74
|
+
assert !@pio.eof?, "EOF should NOT be reached"
|
75
|
+
@pio.read_all
|
76
|
+
|
77
|
+
assert @pio.eof?, "EOF should be reached"
|
78
|
+
end
|
79
|
+
|
80
|
+
|
81
|
+
def test_close
|
82
|
+
@pio.parts.each do |io|
|
83
|
+
assert !io.closed?, "#{io.inspect} should NOT be closed"
|
84
|
+
end
|
85
|
+
|
86
|
+
@pio.close
|
87
|
+
|
88
|
+
@pio.parts.each do |io|
|
89
|
+
assert io.closed?, "#{io.inspect} should be closed"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|