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/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
|
|