blitz 0.1.12 → 0.1.13

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -10,6 +10,7 @@ gem "json_pure", "~> 1.5.1"
10
10
  gem "hexy", "~> 0.1.1"
11
11
  gem 'rspec', '2.6.0'
12
12
  gem 'rspec-core', '2.6.4'
13
+ gem 'term-ansicolor', '1.0.5'
13
14
 
14
15
  # Add dependencies to develop your gem here.
15
16
  # Include everything needed to run rake, tests, features, etc.
@@ -26,6 +26,7 @@ GEM
26
26
  rspec-expectations (2.6.0)
27
27
  diff-lcs (~> 1.1.2)
28
28
  rspec-mocks (2.6.0)
29
+ term-ansicolor (1.0.5)
29
30
 
30
31
  PLATFORMS
31
32
  ruby
@@ -40,3 +41,4 @@ DEPENDENCIES
40
41
  rest-client (~> 1.6.1)
41
42
  rspec (= 2.6.0)
42
43
  rspec-core (= 2.6.4)
44
+ term-ansicolor (= 1.0.5)
data/Rakefile CHANGED
@@ -32,13 +32,6 @@ Jeweler::Tasks.new do |gem|
32
32
  end
33
33
  Jeweler::RubygemsDotOrgTasks.new
34
34
 
35
- require 'rake/testtask'
36
- Rake::TestTask.new(:test) do |test|
37
- test.libs << 'lib' << 'test'
38
- test.pattern = 'test/**/test_*.rb'
39
- test.verbose = true
40
- end
41
-
42
35
  desc "Run all the specs"
43
36
  task :spec do
44
37
  require 'rspec/core'
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{blitz}
8
- s.version = "0.1.12"
8
+ s.version = "0.1.13"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["pcapr"]
12
- s.date = %q{2011-10-05}
12
+ s.date = %q{2011-10-11}
13
13
  s.default_executable = %q{blitz}
14
14
  s.description = %q{Make load and performance testing a fun sport}
15
15
  s.email = %q{support@blitz.io}
@@ -64,6 +64,7 @@ Gem::Specification.new do |s|
64
64
  s.add_runtime_dependency(%q<hexy>, ["~> 0.1.1"])
65
65
  s.add_runtime_dependency(%q<rspec>, ["= 2.6.0"])
66
66
  s.add_runtime_dependency(%q<rspec-core>, ["= 2.6.4"])
67
+ s.add_runtime_dependency(%q<term-ansicolor>, ["= 1.0.5"])
67
68
  s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
68
69
  s.add_development_dependency(%q<jeweler>, ["~> 1.5.1"])
69
70
  else
@@ -74,6 +75,7 @@ Gem::Specification.new do |s|
74
75
  s.add_dependency(%q<hexy>, ["~> 0.1.1"])
75
76
  s.add_dependency(%q<rspec>, ["= 2.6.0"])
76
77
  s.add_dependency(%q<rspec-core>, ["= 2.6.4"])
78
+ s.add_dependency(%q<term-ansicolor>, ["= 1.0.5"])
77
79
  s.add_dependency(%q<bundler>, ["~> 1.0.0"])
78
80
  s.add_dependency(%q<jeweler>, ["~> 1.5.1"])
79
81
  end
@@ -85,6 +87,7 @@ Gem::Specification.new do |s|
85
87
  s.add_dependency(%q<hexy>, ["~> 0.1.1"])
86
88
  s.add_dependency(%q<rspec>, ["= 2.6.0"])
87
89
  s.add_dependency(%q<rspec-core>, ["= 2.6.4"])
90
+ s.add_dependency(%q<term-ansicolor>, ["= 1.0.5"])
88
91
  s.add_dependency(%q<bundler>, ["~> 1.0.0"])
89
92
  s.add_dependency(%q<jeweler>, ["~> 1.5.1"])
90
93
  end
@@ -1,11 +1,12 @@
1
1
  require 'rubygems'
2
+ require 'term/ansicolor'
2
3
  require 'couchrest'
3
4
  require 'hexy'
4
5
  require 'pp'
5
6
 
6
7
  class Blitz # :nodoc:
7
8
  require 'blitz/helper'
8
- Version = "0.1.12"
9
+ Version = "0.1.13"
9
10
 
10
11
  extend Blitz::Helper
11
12
 
@@ -1,6 +1,8 @@
1
1
  class Blitz
2
2
  class Command
3
3
  class Curl < Command # :nodoc:
4
+ include Term::ANSIColor
5
+
4
6
  def cmd_help argv
5
7
  help
6
8
  end
@@ -38,17 +40,17 @@ class Curl < Command # :nodoc:
38
40
  if check['bytes']
39
41
  bytes = '| ' + check['bytes']
40
42
  end
41
- error "#{check['code']} | #{check['url']} #{bytes}"
43
+ error "#{bold(red(check['code']))} | #{cyan(check['url'])} #{bytes}"
42
44
  end
43
45
  error ''
44
46
  end
45
47
  error "If your app is RESTfully built with sinatra or rails, simply add this route:"
46
48
  error ""
47
- error "get '/#{e.uuid}' do"
49
+ error "get '/#{cyan(e.uuid)}' do"
48
50
  error " '42'"
49
51
  error "end"
50
52
  error ""
51
- error "Once this is done, you can blitz #{e.host} all you want."
53
+ error "Once this is done, you can blitz #{cyan(e.host)} all you want."
52
54
  puts
53
55
  end
54
56
 
@@ -59,128 +61,137 @@ class Curl < Command # :nodoc:
59
61
  print_sprint_result args, result
60
62
  rescue ::Blitz::Curl::Error::Authorize => e
61
63
  authorize_error e
64
+ rescue ::Blitz::Curl::Error::Step => e
65
+ error "#{yellow(e.region)}: #{red(e.message)} in step #{e.step}"
66
+ puts
67
+ print_sprint_result args, e
62
68
  rescue ::Blitz::Curl::Error::Region => e
63
- error "#{e.region}: #{e.message}"
69
+ error = "#{yellow(e.region)}: #{red(e.message)}"
64
70
  rescue ::Blitz::Curl::Error => e
65
- error e.message
71
+ error red(e.message)
66
72
  end
67
73
  end
68
-
69
- def print_sprint_result args, result
70
- rtt = result.duration
74
+
75
+ def pretty_print_duration duration
76
+ rtt = duration
71
77
  if rtt < 1.0
72
78
  rtt = (rtt * 1000).floor.to_s + ' ms';
73
79
  else
74
80
  rtt = ("%.2f" % rtt) + ' sec';
75
81
  end
82
+ end
76
83
 
77
- puts "-" * 70
78
- msg "#{result.region}: Response time of #{rtt}"
79
- unless args['dump-header'] or args['verbose']
80
- msg "Try --verbose to see the request/response headers"
81
- end
82
- puts "-" * 70
83
- puts
84
-
85
- if args['dump-header'] or args['verbose']
86
- puts "> " + result.request.line
87
- result.request.headers.each_pair { |k, v| puts "> #{k}: #{v}\r\n" }
84
+ def print_sprint_result args, result
85
+ if result.respond_to? :duration
86
+ rtt = pretty_print_duration result.duration
87
+ msg "#{yellow(result.region)}: Transaction time #{green(rtt)}"
88
88
  puts
89
+ end
89
90
 
90
- content = result.request.content
91
- if not content.empty?
92
- if /^[[:print:]]+$/ =~ content
93
- puts content
94
- else
95
- puts Hexy.new(content).to_s
91
+ result.steps.each do |step|
92
+ req, res = step.request, step.response
93
+ if args['dump-header'] or args['verbose']
94
+ puts "> " + req.line
95
+ req.headers.each_pair { |k, v| puts "> #{k}: #{v}\r\n" }
96
+ puts
97
+
98
+ content = req.content
99
+ if not content.empty?
100
+ if /^[[:print:]]+$/ =~ content
101
+ puts content
102
+ else
103
+ puts Hexy.new(content).to_s
104
+ end
105
+ puts
96
106
  end
107
+
108
+ puts "< " + res.line
109
+ res.headers.each_pair { |k, v| puts "< #{k}: #{v}\r\n" }
97
110
  puts
98
- end
99
-
100
- puts "< " + result.response.line
101
- result.response.headers.each_pair { |k, v| puts "> #{k}: #{v}\r\n" }
102
- puts
103
- end
104
-
105
- content = result.response.content
106
- if not content.empty?
107
- if /^[[:print:]]+$/ =~ content
108
- puts content
111
+ content = res.content
112
+ if not content.empty?
113
+ if /^[[:print:]]+$/ =~ content
114
+ puts content
115
+ else
116
+ puts Hexy.new(content).to_s
117
+ end
118
+ end
109
119
  else
110
- puts Hexy.new(content).to_s
120
+ puts "> " + req.method + ' ' + req.url
121
+ if res
122
+ text = "< " + res.status.to_s + ' ' + res.message
123
+ if step.duration
124
+ text << ' in ' + green(pretty_print_duration(step.duration))
125
+ end
126
+ puts text
127
+ end
128
+ puts
111
129
  end
112
130
  end
113
131
  end
114
132
 
115
133
  def rush args
116
134
  continue = true
135
+ last_index = nil
117
136
  begin
118
137
  [ 'INT', 'STOP', 'HUP' ].each do |s|
119
138
  trap(s) { continue = false }
120
139
  end
121
140
  job = ::Blitz::Curl::Rush.queue args
122
- border = "+-----------+----------+----------+------------+--------------+--------+----------+\n"
123
- msg "rushing from #{job.region}..."
124
- header = true
141
+ msg "rushing from #{yellow(job.region)}..."
142
+ puts
125
143
  job.result do |result|
126
- if header
127
- header = nil
128
- msg border
129
- msg "| time (s) | users | hits/sec | kbytes/sec | latency (ms) | errors | timeouts |\n"
130
- msg border
144
+ print_rush_result args, result, last_index
145
+ if not result.timeline.empty?
146
+ last_index = result.timeline.size
131
147
  end
132
- print_rush_result result
133
- sleep 1.0 if not continue
148
+ sleep 2.0 if not continue
134
149
  continue
135
150
  end
136
- msg border
137
151
  puts
138
- msg "[aborted]" if not continue
152
+ msg "[#{red('aborted')}]" if not continue
139
153
  rescue ::Blitz::Curl::Error::Authorize => e
140
154
  authorize_error e
141
155
  rescue ::Blitz::Curl::Error::Region => e
142
- error "#{e.region}: #{e.message}"
156
+ error "#{yellow(e.region)}: #{red(e.message)}"
143
157
  rescue ::Blitz::Curl::Error => e
144
- error e.message
158
+ error red(e.message)
145
159
  end
146
160
  end
147
161
 
148
- def print_rush_result result
149
- recent = result.timeline[-1]
150
- kilobytes_per_second = 0
151
- hits_per_second = 0
152
- if result.timeline.size > 1
153
- last = result.timeline[-2]
154
- elapsed = recent.timestamp - last.timestamp
155
- hits_per_second = ( recent.hits - last.hits ) / ( recent.timestamp - last.timestamp )
156
- kilobytes_per_second = ( ( recent.rxbytes + recent.txbytes ) - ( last.rxbytes + last.txbytes ) / elapsed ) / 1024
157
- else
158
- hits_per_second = recent.hits/recent.timestamp
159
- kilobytes_per_second = ( ( recent.rxbytes + recent.txbytes )/recent.timestamp ) / 1024
162
+ def print_rush_result args, result, last_index
163
+ if last_index.nil?
164
+ print yellow("%6s " % "Time")
165
+ print "%6s " % "Users"
166
+ print green("%8s " % "Hits")
167
+ print magenta("%8s " % "Timeouts")
168
+ print red("%8s " % "Errors")
169
+ print green("%8s " % "Hits/s")
170
+ print "%s" % "Mbps"
171
+ puts
160
172
  end
161
173
 
162
- output = ['']
163
- output << "%10u " % recent.timestamp
164
- output << "%9u " % recent.volume
165
- output << "%9u " % hits_per_second
166
- output << "%11u " % kilobytes_per_second
167
-
168
- duration = recent.duration * 1000
169
- if duration >= 0
170
- output << "%13u " % duration
171
- else
172
- output << "%13s " % 'no data'
174
+ if last_index and result.timeline.size == last_index
175
+ return
173
176
  end
174
177
 
175
- output << "%7u " % recent.errors
176
- output << "%9u " % recent.timeouts
177
- output << ''
178
-
179
- if recent.volume > 0
180
- $stdout.print output.join('|')
181
- $stdout.print "\n"
178
+ last = result.timeline[-2]
179
+ curr = result.timeline[-1]
180
+ print yellow("%5.1fs " % curr.timestamp)
181
+ print "%6d " % curr.volume
182
+ print green("%8d " % curr.hits)
183
+ print magenta("%8d " % curr.timeouts)
184
+ print red("%8d " % curr.errors)
185
+
186
+ if last
187
+ elapsed = curr.timestamp - last.timestamp
188
+ mbps = ((curr.txbytes + curr.rxbytes) - (last.txbytes + last.rxbytes))/elapsed/1024.0/1024.0
189
+ htps = (curr.hits - last.hits)/elapsed
190
+ print green(" %7.2f " % htps)
191
+ print "%.2f" % mbps
182
192
  end
183
- $stdout.flush
193
+
194
+ print "\n"
184
195
  end
185
196
 
186
197
  def help
@@ -217,169 +228,173 @@ class Curl < Command # :nodoc:
217
228
  end
218
229
 
219
230
  def parse_cli argv
220
- hash = Hash.new
221
- hash['text'] = argv.join(' ')
222
-
231
+ hash = { 'steps' => [] }
232
+
223
233
  while not argv.empty?
224
- break if argv.first[0,1] != '-'
234
+ hash['steps'] << Hash.new
235
+ step = hash['steps'].last
236
+
237
+ while not argv.empty?
238
+ break if argv.first[0,1] != '-'
225
239
 
226
- k = argv.shift
227
- if [ '-A', '--user-agent' ].member? k
228
- hash['user-agent'] = shift(k, argv)
229
- next
230
- end
240
+ k = argv.shift
241
+ if [ '-A', '--user-agent' ].member? k
242
+ step['user-agent'] = shift(k, argv)
243
+ next
244
+ end
231
245
 
232
- if [ '-b', '--cookie' ].member? k
233
- # TODO: support cookie jars
234
- hash['cookies'] ||= []
235
- hash['cookies'] << shift(k, argv)
236
- next
237
- end
246
+ if [ '-b', '--cookie' ].member? k
247
+ step['cookies'] ||= []
248
+ step['cookies'] << shift(k, argv)
249
+ next
250
+ end
238
251
 
239
- if [ '-d', '--data' ].member? k
240
- hash['content'] ||= Hash.new
241
- hash['content']['data'] ||= []
242
- v = shift(k, argv)
243
- v = File.read v[1..-1] if v =~ /^@/
244
- hash['content']['data'] << v
245
- next
246
- end
252
+ if [ '-d', '--data' ].member? k
253
+ step['content'] ||= Hash.new
254
+ step['content']['data'] ||= []
255
+ v = shift(k, argv)
256
+ v = File.read v[1..-1] if v =~ /^@/
257
+ step['content']['data'] << v
258
+ next
259
+ end
247
260
 
248
- if [ '-D', '--dump-header' ].member? k
249
- hash['dump-header'] = shift(k, argv)
250
- next
251
- end
261
+ if [ '-D', '--dump-header' ].member? k
262
+ hash['dump-header'] = shift(k, argv)
263
+ next
264
+ end
252
265
 
253
- if [ '-e', '--referer'].member? k
254
- hash['referer'] = shift(k, argv)
255
- next
256
- end
266
+ if [ '-e', '--referer'].member? k
267
+ step['referer'] = shift(k, argv)
268
+ next
269
+ end
257
270
 
258
- if [ '-h', '--help' ].member? k
259
- hash['help'] = true
260
- next
261
- end
271
+ if [ '-h', '--help' ].member? k
272
+ hash['help'] = true
273
+ next
274
+ end
262
275
 
263
- if [ '-H', '--header' ].member? k
264
- hash['headers'] ||= []
265
- hash['headers'].push shift(k, argv)
266
- next
267
- end
276
+ if [ '-H', '--header' ].member? k
277
+ step['headers'] ||= []
278
+ step['headers'].push shift(k, argv)
279
+ next
280
+ end
268
281
 
269
- if [ '-p', '--pattern' ].member? k
270
- v = shift(k, argv)
271
- if not /^(\d+)-(\d+):(\d+)$/ =~ v
272
- raise Test::Unit::AssertionFailedError, "invalid ramp pattern"
282
+ if [ '-p', '--pattern' ].member? k
283
+ v = shift(k, argv)
284
+ v.split(',').each do |vt|
285
+ unless /^(\d+)-(\d+):(\d+)$/ =~ vt
286
+ raise Test::Unit::AssertionFailedError, "invalid ramp pattern"
287
+ end
288
+ hash['pattern'] ||= { 'iterations' => 1, 'intervals' => [] }
289
+ hash['pattern']['intervals'] << {
290
+ 'iterations' => 1,
291
+ 'start' => $1.to_i,
292
+ 'end' => $2.to_i,
293
+ 'duration' => $3.to_i
294
+ }
295
+ end
296
+ next
273
297
  end
274
- hash['pattern'] = {
275
- 'iterations' => 1,
276
- 'intervals' => [{
277
- 'iterations' => 1,
278
- 'start' => $1.to_i,
279
- 'end' => $2.to_i,
280
- 'duration' => $3.to_i
281
- }]
282
- }
283
- next
284
- end
285
298
 
286
- if [ '-r', '--region' ].member? k
287
- v = shift(k, argv)
288
- assert_match(/^california|virginia|singapore|ireland|japan$/, v, 'region must be one of california, virginia, singapore, japan or ireland')
289
- hash['region'] = v
290
- next
291
- end
299
+ if [ '-r', '--region' ].member? k
300
+ v = shift(k, argv)
301
+ assert_match(/^california|virginia|singapore|ireland|japan$/, v, 'region must be one of california, virginia, singapore, japan or ireland')
302
+ hash['region'] = v
303
+ next
304
+ end
292
305
 
293
- if [ '-s', '--status' ].member? k
294
- hash['status'] = shift(k, argv).to_i
295
- next
296
- end
306
+ if [ '-s', '--status' ].member? k
307
+ step['status'] = shift(k, argv).to_i
308
+ next
309
+ end
297
310
 
298
- if [ '-T', '--timeout' ].member? k
299
- hash['timeout'] = shift(k, argv).to_i
300
- next
301
- end
311
+ if [ '-T', '--timeout' ].member? k
312
+ step['timeout'] = shift(k, argv).to_i
313
+ next
314
+ end
302
315
 
303
- if [ '-u', '--user' ].member? k
304
- hash['user'] = shift(k, argv)
305
- next
306
- end
316
+ if [ '-u', '--user' ].member? k
317
+ step['user'] = shift(k, argv)
318
+ next
319
+ end
307
320
 
308
- if [ '-X', '--request' ].member? k
309
- hash['request'] = shift(k, argv)
310
- next
311
- end
321
+ if [ '-X', '--request' ].member? k
322
+ step['request'] = shift(k, argv)
323
+ next
324
+ end
312
325
 
313
- if /-v:(\S+)/ =~ k or /--variable:(\S+)/ =~ k
314
- variable_name = $1
315
- variable_parameter = shift(k, argv)
316
-
317
- assert_match(/^[a-zA-Z][a-zA-Z0-9]*$/, variable_name, "variable name must be alphanumeric: #{variable_name}")
318
-
319
- hash['variables'] ||= Hash.new
320
-
321
- parameter_hash = {}
322
- if variable_parameter.match(/^(list)?\[([^\]]+)\]$/)
323
- parameter_hash['type'] = 'list'
324
- parameter_hash['entries'] = $2.split(',')
325
- elsif variable_parameter.match(/^(a|alpha)$/)
326
- parameter_hash['type'] = 'alpha'
327
- elsif variable_parameter.match(/^(a|alpha)\[(\d+),(\d+)(,(\d+))??\]$/)
328
- parameter_hash['type'] = 'alpha'
329
- parameter_hash['min'] = $2.to_i
330
- parameter_hash['max'] = $3.to_i
331
- parameter_hash['count'] = $5 ? $5.to_i : 1000
332
- elsif variable_parameter.match(/^(n|number)$/)
333
- parameter_hash['type'] = 'number'
334
- elsif variable_parameter.match(/^(n|number)\[(-?\d+),(-?\d+)(,(\d+))?\]$/)
335
- parameter_hash['type'] = 'number'
336
- parameter_hash['min'] = $2.to_i
337
- parameter_hash['max'] = $3.to_i
338
- parameter_hash['count'] = $5 ? $5.to_i : 1000
339
- elsif variable_parameter.match(/^(u|udid)$/)
340
- parameter_hash['type'] = 'udid'
341
- else
342
- raise ArgumentError, "Invalid variable parameter to #{variable_name}: #{variable_parameter}"
326
+ if /-v:(\S+)/ =~ k or /--variable:(\S+)/ =~ k
327
+ vname = $1
328
+ vargs = shift(k, argv)
329
+
330
+ assert_match /^[a-zA-Z][a-zA-Z0-9]*$/, vname, "variable name must be alphanumeric: #{vname}"
331
+
332
+ step['variables'] ||= Hash.new
333
+ vhash = step['variables'][vname] = Hash.new
334
+ if vargs.match /^(list)?\[([^\]]+)\]$/
335
+ vhash['type'] = 'list'
336
+ vhash['entries'] = $2.split(',')
337
+ elsif vargs.match /^(a|alpha)$/
338
+ vhash['type'] = 'alpha'
339
+ elsif vargs.match /^(a|alpha)\[(\d+),(\d+)(,(\d+))??\]$/
340
+ vhash['type'] = 'alpha'
341
+ vhash['min'] = $2.to_i
342
+ vhash['max'] = $3.to_i
343
+ vhash['count'] = $5 ? $5.to_i : 1000
344
+ elsif vargs.match /^(n|number)$/
345
+ vhash['type'] = 'number'
346
+ elsif vargs.match /^(n|number)\[(-?\d+),(-?\d+)(,(\d+))?\]$/
347
+ vhash['type'] = 'number'
348
+ vhash['min'] = $2.to_i
349
+ vhash['max'] = $3.to_i
350
+ vhash['count'] = $5 ? $5.to_i : 1000
351
+ elsif vargs.match /^(u|udid)$/
352
+ vhash['type'] = 'udid'
353
+ else
354
+ raise ArgumentError, "Invalid variable args for #{vname}: #{vargs}"
355
+ end
356
+ next
343
357
  end
344
358
 
345
- hash['variables'][variable_name] = parameter_hash
346
- next
347
- end
359
+ if [ '-V', '--verbose' ].member? k
360
+ hash['verbose'] = true
361
+ next
362
+ end
348
363
 
349
- if [ '-V', '--verbose' ].member? k
350
- hash['verbose'] = true
351
- next
352
- end
364
+ if [ '-1', '--tlsv1' ].member? k
365
+ step['ssl'] = 'tlsv1'
366
+ next
367
+ end
353
368
 
354
- if [ '-1', '--tlsv1' ].member? k
355
- hash['ssl'] = 'tlsv1'
356
- next
357
- end
369
+ if [ '-2', '--sslv2' ].member? k
370
+ step['ssl'] = 'sslv2'
371
+ next
372
+ end
358
373
 
359
- if [ '-2', '--sslv2' ].member? k
360
- hash['ssl'] = 'sslv2'
361
- next
362
- end
374
+ if [ '-3', '--sslv3' ].member? k
375
+ step['ssl'] = 'sslv3'
376
+ next
377
+ end
363
378
 
364
- if [ '-3', '--sslv3' ].member? k
365
- hash['ssl'] = 'sslv3'
366
- next
379
+ raise ArgumentError, "Unknown option #{k}"
367
380
  end
368
381
 
369
- raise ArgumentError, "Unknown option #{k}"
382
+ if step.member? 'content'
383
+ data_size = step['content']['data'].inject(0) { |m, v| m + v.size }
384
+ assert(data_size < 10*1024, "POST content must be < 10K")
385
+ end
386
+
387
+ break if hash['help']
388
+
389
+ url = argv.shift
390
+ raise ArgumentError, "no URL specified!" if not url
391
+ step['url'] = url
370
392
  end
371
-
393
+
372
394
  if not hash['help']
373
- url = argv.shift
374
- if not url
395
+ if hash['steps'].empty?
375
396
  raise ArgumentError, "no URL specified!"
376
397
  end
377
- hash['url'] = url
378
- end
379
-
380
- if hash.member? 'content'
381
- data_size = hash['content']['data'].inject(0) { |m, v| m + v.size }
382
- assert(data_size < 10*1024, "POST content must be < 10K")
383
398
  end
384
399
 
385
400
  hash