kronk 1.8.7 → 1.9.0
Sign up to get free protection for your applications and to get access to all the features.
- 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/player/suite.rb
CHANGED
@@ -15,7 +15,7 @@ class Kronk
|
|
15
15
|
@stop_time = Time.now
|
16
16
|
@mutex.synchronize do
|
17
17
|
render
|
18
|
-
$stdout.puts "Elapsed:
|
18
|
+
$stdout.puts "Elapsed: #{(Time.now - @start_time).round 3}s"
|
19
19
|
|
20
20
|
req = @current.responses[-1].request ||
|
21
21
|
@current.responses[0].request if @current
|
@@ -24,7 +24,8 @@ class Kronk
|
|
24
24
|
meth = req.http_method
|
25
25
|
path = req.uri.request_uri
|
26
26
|
time = req.response.time.round 3
|
27
|
-
$stdout.puts "Current
|
27
|
+
$stdout.puts "Current Conns: #{Kronk::HTTP.conn_count}"
|
28
|
+
$stdout.puts "Current Req: #{meth} #{path} (#{time}s)"
|
28
29
|
end
|
29
30
|
end
|
30
31
|
end
|
@@ -40,8 +41,11 @@ class Kronk
|
|
40
41
|
text = diff_text kronk if status == "F"
|
41
42
|
time =
|
42
43
|
(kronk.responses[0].time.to_f + kronk.responses[1].time.to_f) / 2
|
44
|
+
ctime =
|
45
|
+
(kronk.responses[0].conn_time.to_f +
|
46
|
+
kronk.responses[1].conn_time.to_f) / 2
|
43
47
|
|
44
|
-
[status, time, text]
|
48
|
+
[status, time, ctime, text]
|
45
49
|
|
46
50
|
elsif kronk.response
|
47
51
|
begin
|
@@ -54,7 +58,7 @@ class Kronk
|
|
54
58
|
|
55
59
|
status = "F" if !kronk.response.success?
|
56
60
|
text = resp_text kronk if status == "F"
|
57
|
-
[status, kronk.response.time, text]
|
61
|
+
[status, kronk.response.time, kronk.response.conn_time, text]
|
58
62
|
end
|
59
63
|
|
60
64
|
@mutex.synchronize do
|
@@ -69,7 +73,7 @@ class Kronk
|
|
69
73
|
|
70
74
|
def error err, kronk=nil
|
71
75
|
status = "E"
|
72
|
-
result = [status, 0, error_text(err, kronk)]
|
76
|
+
result = [status, 0, 0, error_text(err, kronk)]
|
73
77
|
@mutex.synchronize{ @results << result }
|
74
78
|
|
75
79
|
$stdout << status
|
@@ -87,15 +91,17 @@ class Kronk
|
|
87
91
|
def render
|
88
92
|
player_time = @stop_time - @start_time
|
89
93
|
total_time = 0
|
94
|
+
total_ctime = 0
|
90
95
|
bad_count = 0
|
91
96
|
failure_count = 0
|
92
97
|
error_count = 0
|
93
98
|
err_buffer = ""
|
94
99
|
|
95
|
-
@results.each do |(status, time, text)|
|
100
|
+
@results.each do |(status, time, ctime, text)|
|
96
101
|
case status
|
97
102
|
when "F"
|
98
103
|
total_time += time.to_f
|
104
|
+
total_ctime += ctime.to_f
|
99
105
|
bad_count += 1
|
100
106
|
failure_count += 1
|
101
107
|
err_buffer << "\n #{bad_count}) Failure:\n#{text}"
|
@@ -106,24 +112,30 @@ class Kronk
|
|
106
112
|
err_buffer << "\n #{bad_count}) Error:\n#{text}"
|
107
113
|
|
108
114
|
else
|
109
|
-
total_time
|
115
|
+
total_time += time.to_f
|
116
|
+
total_ctime += ctime.to_f
|
110
117
|
end
|
111
118
|
end
|
112
119
|
|
113
120
|
non_error_count = @results.length - error_count
|
114
121
|
|
115
|
-
avg_time
|
122
|
+
avg_time = non_error_count > 0 ?
|
116
123
|
((total_time / non_error_count)* 1000).round(3) : "n/a "
|
117
124
|
|
118
|
-
|
125
|
+
avg_ctime = non_error_count > 0 ?
|
126
|
+
((total_ctime / non_error_count)* 1000).round(3) : "n/a "
|
127
|
+
|
128
|
+
avg_qps = non_error_count > 0 ?
|
119
129
|
(non_error_count / player_time).round(3) : "n/a"
|
120
130
|
|
121
131
|
$stderr.puts err_buffer unless err_buffer.empty?
|
122
132
|
$stdout.puts "\n#{@results.length} cases, " +
|
123
133
|
"#{failure_count} failures, #{error_count} errors"
|
124
134
|
|
125
|
-
$stdout.puts "
|
126
|
-
$stdout.puts "Avg
|
135
|
+
$stdout.puts "Total Conns: #{Kronk::HTTP.total_conn}"
|
136
|
+
$stdout.puts "Avg Conn Time: #{avg_ctime}ms"
|
137
|
+
$stdout.puts "Avg Time: #{avg_time}ms"
|
138
|
+
$stdout.puts "Avg QPS: #{avg_qps}"
|
127
139
|
|
128
140
|
return bad_count == 0
|
129
141
|
end
|
data/lib/kronk/request.rb
CHANGED
@@ -13,17 +13,18 @@ class Kronk
|
|
13
13
|
REQUEST_LINE_MATCHER =
|
14
14
|
%r{(?:^|[\s'"])(?:([a-z]+)\s)?(?:(https?://[^/]+)(/[^\s'";]*)?|(/[^\s'";]*))}i
|
15
15
|
|
16
|
+
|
16
17
|
##
|
17
18
|
# Creates a query string from data.
|
18
19
|
|
19
|
-
def self.build_query data, param=nil
|
20
|
+
def self.build_query data, param=nil, &block
|
20
21
|
return data.to_s unless param || Hash === data
|
21
22
|
|
22
23
|
case data
|
23
24
|
when Array
|
24
25
|
out = data.map do |value|
|
25
26
|
key = "#{param}[]"
|
26
|
-
build_query value, key
|
27
|
+
build_query value, key, &block
|
27
28
|
end
|
28
29
|
|
29
30
|
out.join "&"
|
@@ -31,12 +32,13 @@ class Kronk
|
|
31
32
|
when Hash
|
32
33
|
out = data.map do |key, value|
|
33
34
|
key = param.nil? ? key : "#{param}[#{key}]"
|
34
|
-
build_query value, key
|
35
|
+
build_query value, key, &block
|
35
36
|
end
|
36
37
|
|
37
38
|
out.join "&"
|
38
39
|
|
39
40
|
else
|
41
|
+
yield param.to_s, data if block_given?
|
40
42
|
"#{param}=#{data}"
|
41
43
|
end
|
42
44
|
end
|
@@ -193,7 +195,11 @@ class Kronk
|
|
193
195
|
|
194
196
|
|
195
197
|
class << self
|
196
|
-
|
198
|
+
# The boundary to use for multipart requests; default: AaB03x
|
199
|
+
attr_accessor :multipart_boundary
|
200
|
+
|
201
|
+
|
202
|
+
%w{GET POST PUT PATCH DELETE TRACE HEAD OPTIONS}.each do |name|
|
197
203
|
class_eval <<-"END"
|
198
204
|
def #{name} uri, opts={}, &block
|
199
205
|
opts[:http_method] = "#{name}"
|
@@ -203,10 +209,12 @@ class Kronk
|
|
203
209
|
end
|
204
210
|
end
|
205
211
|
|
212
|
+
self.multipart_boundary = 'AaB03x'
|
213
|
+
|
206
214
|
|
207
215
|
attr_accessor :headers, :proxy, :response, :timeout
|
208
216
|
|
209
|
-
attr_reader :body, :http_method, :uri, :use_cookies
|
217
|
+
attr_reader :body, :http_method, :proxy, :uri, :use_cookies
|
210
218
|
|
211
219
|
##
|
212
220
|
# Build an http request to the given uri and return a Response instance.
|
@@ -231,7 +239,6 @@ class Kronk
|
|
231
239
|
@connection = nil
|
232
240
|
@response = nil
|
233
241
|
@body = nil
|
234
|
-
@_req = nil
|
235
242
|
|
236
243
|
@headers = opts[:headers] || {}
|
237
244
|
|
@@ -241,18 +248,24 @@ class Kronk
|
|
241
248
|
].flatten.compact.uniq.join(",")
|
242
249
|
@headers.delete "Accept-Encoding" if @headers["Accept-Encoding"].empty?
|
243
250
|
|
251
|
+
@headers['Connection'] ||= 'Keep-Alive'
|
252
|
+
|
244
253
|
@timeout = opts[:timeout] || Kronk.config[:timeout]
|
245
254
|
|
246
255
|
@uri = self.class.build_uri uri, opts
|
247
256
|
|
248
|
-
|
249
|
-
@proxy = {:host => @proxy} unless Hash === @proxy
|
250
|
-
|
257
|
+
self.proxy = opts[:proxy]
|
251
258
|
|
252
259
|
if opts[:file]
|
253
|
-
self.body =
|
260
|
+
self.body = opts[:file].respond_to?(:read) ?
|
261
|
+
opts[:file] : File.open(opts[:file], 'rb')
|
262
|
+
|
263
|
+
elsif opts[:form_upload]
|
264
|
+
self.body = build_multipart opts
|
265
|
+
|
254
266
|
elsif opts[:form]
|
255
267
|
self.form_data = opts[:form]
|
268
|
+
|
256
269
|
elsif opts[:data]
|
257
270
|
self.body = opts[:data]
|
258
271
|
end
|
@@ -284,54 +297,47 @@ class Kronk
|
|
284
297
|
|
285
298
|
|
286
299
|
##
|
287
|
-
# Assign request body. Supports String, Hash, and IO.
|
300
|
+
# Assign request body. Supports String, Hash, and IO. Will attempt to
|
301
|
+
# correctly assing the Content-Type and Transfer-Encoding headers.
|
288
302
|
|
289
303
|
def body= data
|
290
|
-
|
291
|
-
|
292
|
-
self.form_data = data
|
304
|
+
if data.respond_to?(:read) || Kronk::Multipart === data
|
305
|
+
ctype = "application/binary"
|
293
306
|
|
294
|
-
|
295
|
-
|
296
|
-
|
307
|
+
if data.respond_to?(:path)
|
308
|
+
types = MIME::Types.of File.extname(data.path.to_s)[1..-1].to_s
|
309
|
+
ctype = types[0].to_s unless types.empty?
|
297
310
|
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
ext ||= "binary"
|
311
|
+
elsif Kronk::Multipart === data
|
312
|
+
ctype = "multipart/form-data, boundary=#{data.boundary}"
|
313
|
+
end
|
302
314
|
|
303
|
-
|
315
|
+
@headers['Content-Type'] = ctype
|
304
316
|
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
end
|
310
|
-
end
|
317
|
+
@body = data
|
318
|
+
|
319
|
+
elsif Hash === data
|
320
|
+
self.form_data = data
|
311
321
|
|
312
|
-
|
313
|
-
|
322
|
+
else
|
323
|
+
@body = data.to_s
|
324
|
+
end
|
314
325
|
|
315
326
|
@body
|
316
327
|
end
|
317
328
|
|
318
329
|
|
319
330
|
##
|
320
|
-
#
|
331
|
+
# Retrieve or create an HTTP connection instance.
|
321
332
|
|
322
333
|
def connection
|
323
|
-
|
324
|
-
|
334
|
+
conn = Kronk::HTTP.new @uri.host, @uri.port,
|
335
|
+
:proxy => self.proxy,
|
336
|
+
:ssl => !!(@uri.scheme =~ /^https$/)
|
325
337
|
|
326
|
-
|
327
|
-
@connection.open_timeout = @connection.read_timeout = @timeout if @timeout
|
338
|
+
conn.open_timeout = conn.read_timeout = @timeout if @timeout
|
328
339
|
|
329
|
-
|
330
|
-
require 'net/https'
|
331
|
-
@connection.use_ssl = true
|
332
|
-
end
|
333
|
-
|
334
|
-
@connection
|
340
|
+
conn
|
335
341
|
end
|
336
342
|
|
337
343
|
|
@@ -347,12 +353,28 @@ class Kronk
|
|
347
353
|
# Assigns body of the request with form headers.
|
348
354
|
|
349
355
|
def form_data= data
|
350
|
-
dont_chunk!
|
351
356
|
@headers['Content-Type'] = "application/x-www-form-urlencoded"
|
352
357
|
@body = self.class.build_query data
|
353
358
|
end
|
354
359
|
|
355
360
|
|
361
|
+
##
|
362
|
+
# Assign proxy options.
|
363
|
+
|
364
|
+
def proxy= prox_opts
|
365
|
+
@proxy = {}
|
366
|
+
|
367
|
+
if prox_opts && !prox_opts.empty?
|
368
|
+
@proxy = prox_opts
|
369
|
+
@proxy = {:host => @proxy.to_s} unless Hash === @proxy
|
370
|
+
@proxy[:host], port = @proxy[:host].split ":"
|
371
|
+
@proxy[:port] ||= port || 8080
|
372
|
+
end
|
373
|
+
|
374
|
+
@proxy
|
375
|
+
end
|
376
|
+
|
377
|
+
|
356
378
|
##
|
357
379
|
# Assigns the http method.
|
358
380
|
|
@@ -422,18 +444,17 @@ class Kronk
|
|
422
444
|
# Options are passed directly to the Kronk::Response constructor.
|
423
445
|
|
424
446
|
def retrieve opts={}, &block
|
425
|
-
start_time =
|
426
|
-
opts = opts.merge :request => self
|
447
|
+
start_time = Time.now
|
427
448
|
|
428
|
-
@response =
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
res.time = Time.now - start_time
|
433
|
-
res.request = self
|
434
|
-
res
|
449
|
+
@response = stream opts
|
450
|
+
|
451
|
+
if block_given?
|
452
|
+
block = lambda{|chunk| yield @response, chunk }
|
435
453
|
end
|
436
454
|
|
455
|
+
@response.body(&block) # make sure to read the full body from io
|
456
|
+
@response.time = Time.now - start_time - @response.conn_time
|
457
|
+
|
437
458
|
@response
|
438
459
|
end
|
439
460
|
|
@@ -448,12 +469,26 @@ class Kronk
|
|
448
469
|
# request.connection.finish
|
449
470
|
|
450
471
|
def stream opts={}
|
451
|
-
|
452
|
-
http = connection.started? ? connection : connection.start
|
453
|
-
@response = http.request http_request, @body, opts
|
454
|
-
@response.request = self
|
472
|
+
retried = false
|
455
473
|
|
456
|
-
|
474
|
+
begin
|
475
|
+
start_time = Time.now
|
476
|
+
conn = connection
|
477
|
+
conn.start unless conn.started?
|
478
|
+
conn_time = Time.now - start_time
|
479
|
+
|
480
|
+
@response = conn.request http_request, nil, opts
|
481
|
+
@response.conn_time = conn_time
|
482
|
+
@response.request = self
|
483
|
+
|
484
|
+
@response
|
485
|
+
|
486
|
+
rescue EOFError, Errno::EPIPE
|
487
|
+
raise if retried
|
488
|
+
@connection = nil
|
489
|
+
retried = true
|
490
|
+
retry
|
491
|
+
end
|
457
492
|
end
|
458
493
|
|
459
494
|
|
@@ -472,8 +507,8 @@ class Kronk
|
|
472
507
|
|
473
508
|
hash[:auth] = @auth if @auth
|
474
509
|
hash[:data] = @body if @body
|
475
|
-
hash[:headers] = @headers
|
476
|
-
hash[:proxy] =
|
510
|
+
hash[:headers] = @headers unless @headers.empty?
|
511
|
+
hash[:proxy] = self.proxy unless self.proxy.empty?
|
477
512
|
|
478
513
|
hash
|
479
514
|
end
|
@@ -481,6 +516,8 @@ class Kronk
|
|
481
516
|
|
482
517
|
##
|
483
518
|
# Returns the raw HTTP request String.
|
519
|
+
# Warning: If the body is an IO instance or a Multipart instance,
|
520
|
+
# the full input will be read.
|
484
521
|
|
485
522
|
def to_s
|
486
523
|
out = "#{@http_method} #{@uri.request_uri} HTTP/1.1\r\n"
|
@@ -491,7 +528,14 @@ class Kronk
|
|
491
528
|
end
|
492
529
|
|
493
530
|
out << "\r\n"
|
494
|
-
|
531
|
+
|
532
|
+
if @body.respond_to?(:read)
|
533
|
+
out << @body.read
|
534
|
+
elsif Kronk::Multipart === @body
|
535
|
+
out << @body.to_io.read
|
536
|
+
else
|
537
|
+
out << @body.to_s
|
538
|
+
end
|
495
539
|
end
|
496
540
|
|
497
541
|
|
@@ -512,37 +556,60 @@ class Kronk
|
|
512
556
|
req.basic_auth @auth[:username], @auth[:password] if
|
513
557
|
@auth && @auth[:username]
|
514
558
|
|
515
|
-
|
559
|
+
# Stream Multipart
|
560
|
+
if Kronk::Multipart === @body
|
561
|
+
req.body_stream = @body.to_io
|
562
|
+
|
563
|
+
# Stream IO
|
564
|
+
elsif @body.respond_to?(:read)
|
565
|
+
req.body_stream = @body
|
566
|
+
|
567
|
+
else
|
568
|
+
req.body = @body
|
569
|
+
end
|
570
|
+
|
571
|
+
b = req.body || req.body_stream
|
572
|
+
|
573
|
+
if b.respond_to?(:bytesize)
|
574
|
+
req['Content-Length'] = b.bytesize.to_s
|
575
|
+
elsif b.respond_to?(:size) && b.size
|
576
|
+
req['Content-Length'] = b.size.to_s
|
577
|
+
elsif b.nil?
|
578
|
+
req['Content-Length'] = "0"
|
579
|
+
end
|
580
|
+
|
581
|
+
req['Transfer-Encoding'] = 'chunked' if !req['Content-Length']
|
516
582
|
|
517
583
|
req
|
518
584
|
end
|
519
585
|
|
520
586
|
|
521
|
-
|
522
|
-
# Assign the use of a proxy.
|
523
|
-
# The proxy_opts arg can be a uri String or a Hash with the :address key
|
524
|
-
# and optional :username and :password keys.
|
587
|
+
private
|
525
588
|
|
526
|
-
def http_proxy addr, opts={}
|
527
|
-
return Kronk::HTTP unless addr
|
528
589
|
|
529
|
-
|
530
|
-
port ||= opts[:port] || 8080
|
590
|
+
QUERY_SCANNER = /(?:^|&)([^=&]+)(?:=([^&]+))?/
|
531
591
|
|
532
|
-
|
533
|
-
|
592
|
+
def build_multipart opts
|
593
|
+
multi = Kronk::Multipart.new self.class.multipart_boundary
|
534
594
|
|
535
|
-
|
595
|
+
process_query_or_hash(opts[:form]){|name, value| multi.add name, value }
|
536
596
|
|
537
|
-
|
538
|
-
|
597
|
+
process_query_or_hash(opts[:form_upload]) do |name, value|
|
598
|
+
value = File.open(value, 'rb') unless
|
599
|
+
value.respond_to?(:read)
|
600
|
+
multi.add name, value
|
601
|
+
end
|
539
602
|
|
540
|
-
|
603
|
+
multi
|
604
|
+
end
|
541
605
|
|
542
606
|
|
543
|
-
def
|
544
|
-
|
545
|
-
|
607
|
+
def process_query_or_hash qr, &block
|
608
|
+
if Hash === qr
|
609
|
+
self.class.build_query(qr){|name, value| yield name, value }
|
610
|
+
else
|
611
|
+
qr.to_s.scan(QUERY_SCANNER){|(name, value)| yield name, value }
|
612
|
+
end
|
546
613
|
end
|
547
614
|
|
548
615
|
|