kronk 1.8.7 → 1.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- # Size of the raw body in bytes.
174
+ # Current size of the http body in bytes.
173
175
 
174
176
  def bytes
175
- (headers["content-length"] || self.raw_body.bytes.count).to_i
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
- @encoding = "utf-8" unless headers["content-type"]
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 ||= "ASCII-8BIT"
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/html"
313
- "#<#{self.class}:#{@code} #{content_type} #{total_bytes}bytes>"
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?('keep-alive') ||
333
- @headers['proxy-connection'].to_s.include?('keep-alive')
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 headers["content-type"]
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 : raw.split("\r\n\r\n", 2)[1]
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
- ext = File === buff_io.io ?
742
- File.extname(buff_io.io.path)[1..-1] : "html"
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 : "UTF-8"
782
+ buff_io.io.external_encoding : DEFAULT_ENCODING
746
783
  @headers = {
747
- 'content-type' => "text/#{ext}; charset=#{encoding}",
784
+ 'content-type' => "#{ctype[0]}; charset=#{encoding}",
748
785
  }
749
786
 
750
787
  @headless = true
@@ -1,7 +1,7 @@
1
1
  HTTP/1.1 200 OK
2
2
  Server: nginx/0.6.39
3
3
  Date: Fri, 03 Dec 2010 21:49:00 GMT
4
- Content-Type: application/x-plist; charset=utf-8
4
+ Content-Type: application/foobar+x-plist; charset=utf-8
5
5
  Connection: keep-alive
6
6
  Keep-Alive: timeout=20
7
7
  Status: 200 OK
@@ -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}
@@ -160,33 +160,43 @@ ensure
160
160
  end
161
161
 
162
162
 
163
- def expect_request req_method, url, options={}
163
+ def expect_request req_method, url, opts={}
164
164
  uri = URI.parse url
165
165
 
166
- resp = Kronk::Response.new(options[:returns] || mock_200_response)
167
- resp.stubs(:code).returns(options[:status] || '200')
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 = options[:data]
173
+ data = opts[:data]
174
174
  data &&= Hash === data ? Kronk::Request.build_query(data) : data.to_s
175
175
 
176
- headers = options[:headers] || Hash.new
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
- req.expects(:start).yields(http).returns resp
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
- Kronk::HTTP.expects(:new).with(uri.host, uri.port).returns req
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