kronk 1.8.7 → 1.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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