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.
@@ -15,7 +15,7 @@ class Kronk
15
15
  @stop_time = Time.now
16
16
  @mutex.synchronize do
17
17
  render
18
- $stdout.puts "Elapsed: #{(Time.now - @start_time).round 3}s"
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 Req: #{meth} #{path} (#{time}s)"
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 += time.to_f
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 = non_error_count > 0 ?
122
+ avg_time = non_error_count > 0 ?
116
123
  ((total_time / non_error_count)* 1000).round(3) : "n/a "
117
124
 
118
- avg_qps = non_error_count > 0 ?
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 "Avg Time: #{avg_time}ms"
126
- $stdout.puts "Avg QPS: #{avg_qps}"
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
@@ -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
- %w{get post put delete trace head options}.each do |name|
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
- @proxy = opts[:proxy] || {}
249
- @proxy = {:host => @proxy} unless Hash === @proxy
250
-
257
+ self.proxy = opts[:proxy]
251
258
 
252
259
  if opts[:file]
253
- self.body = File.open(opts[:file], 'rb')
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
- case data
291
- when Hash
292
- self.form_data = data
304
+ if data.respond_to?(:read) || Kronk::Multipart === data
305
+ ctype = "application/binary"
293
306
 
294
- when String
295
- dont_chunk!
296
- @body = data
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
- else
299
- if data.respond_to?(:read)
300
- ext = File.extname(data.path.to_s)[1..-1] if data.respond_to?(:path)
301
- ext ||= "binary"
311
+ elsif Kronk::Multipart === data
312
+ ctype = "multipart/form-data, boundary=#{data.boundary}"
313
+ end
302
314
 
303
- @headers['Content-Type'] = "application/#{ext}"
315
+ @headers['Content-Type'] = ctype
304
316
 
305
- @body = data
306
- else
307
- dont_chunk!
308
- @body = data.to_s
309
- end
310
- end
317
+ @body = data
318
+
319
+ elsif Hash === data
320
+ self.form_data = data
311
321
 
312
- @headers['Content-Length'] = @body.size.to_s if @body.respond_to?(:size)
313
- @headers['Transfer-Encoding'] = 'chunked' if !@headers['Content-Length']
322
+ else
323
+ @body = data.to_s
324
+ end
314
325
 
315
326
  @body
316
327
  end
317
328
 
318
329
 
319
330
  ##
320
- # Reference to the HTTP connection instance.
331
+ # Retrieve or create an HTTP connection instance.
321
332
 
322
333
  def connection
323
- return @connection if @connection
324
- http_class = http_proxy @proxy[:host], @proxy
334
+ conn = Kronk::HTTP.new @uri.host, @uri.port,
335
+ :proxy => self.proxy,
336
+ :ssl => !!(@uri.scheme =~ /^https$/)
325
337
 
326
- @connection = http_class.new @uri.host, @uri.port
327
- @connection.open_timeout = @connection.read_timeout = @timeout if @timeout
338
+ conn.open_timeout = conn.read_timeout = @timeout if @timeout
328
339
 
329
- if @uri.scheme =~ /^https$/
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 = nil
426
- opts = opts.merge :request => self
447
+ start_time = Time.now
427
448
 
428
- @response = connection.start do |http|
429
- start_time = Time.now
430
- res = http.request http_request, nil, opts, &block
431
- res.body # make sure to read the full body from io
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
- opts = opts.merge :request => self
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
- @response
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 unless @headers.empty?
476
- hash[:proxy] = @proxy unless @proxy.empty?
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
- out << @body.to_s
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
- @body.respond_to?(:read) ? req.body_stream = @body : req.body = @body
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
- host, port = addr.split ":"
530
- port ||= opts[:port] || 8080
590
+ QUERY_SCANNER = /(?:^|&)([^=&]+)(?:=([^&]+))?/
531
591
 
532
- user = opts[:username]
533
- pass = opts[:password]
592
+ def build_multipart opts
593
+ multi = Kronk::Multipart.new self.class.multipart_boundary
534
594
 
535
- Kronk::Cmd.verbose "Using proxy #{addr}\n" if host && defined?(Cmd)
595
+ process_query_or_hash(opts[:form]){|name, value| multi.add name, value }
536
596
 
537
- Kronk::HTTP::Proxy host, port, user, pass
538
- end
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
- private
603
+ multi
604
+ end
541
605
 
542
606
 
543
- def dont_chunk!
544
- @headers.delete('Transfer-Encoding') if
545
- @headers['Transfer-Encoding'].to_s.downcase == 'chunked'
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