rack 1.5.5 → 1.6.0.beta
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of rack might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/KNOWN-ISSUES +14 -0
- data/README.rdoc +10 -6
- data/Rakefile +3 -4
- data/SPEC +59 -23
- data/lib/rack.rb +2 -1
- data/lib/rack/auth/abstract/request.rb +1 -1
- data/lib/rack/auth/basic.rb +1 -1
- data/lib/rack/auth/digest/md5.rb +1 -1
- data/lib/rack/backports/uri/common_18.rb +1 -1
- data/lib/rack/builder.rb +19 -4
- data/lib/rack/cascade.rb +2 -2
- data/lib/rack/chunked.rb +12 -1
- data/lib/rack/commonlogger.rb +13 -5
- data/lib/rack/conditionalget.rb +14 -2
- data/lib/rack/content_length.rb +5 -1
- data/lib/rack/deflater.rb +52 -13
- data/lib/rack/directory.rb +8 -2
- data/lib/rack/etag.rb +14 -6
- data/lib/rack/file.rb +10 -14
- data/lib/rack/handler.rb +2 -0
- data/lib/rack/handler/fastcgi.rb +4 -1
- data/lib/rack/handler/mongrel.rb +8 -2
- data/lib/rack/handler/scgi.rb +4 -1
- data/lib/rack/handler/thin.rb +8 -2
- data/lib/rack/handler/webrick.rb +46 -6
- data/lib/rack/head.rb +7 -2
- data/lib/rack/lint.rb +73 -25
- data/lib/rack/lobster.rb +8 -3
- data/lib/rack/methodoverride.rb +14 -3
- data/lib/rack/mime.rb +1 -15
- data/lib/rack/mock.rb +15 -7
- data/lib/rack/multipart.rb +2 -2
- data/lib/rack/multipart/parser.rb +107 -53
- data/lib/rack/multipart/uploaded_file.rb +2 -2
- data/lib/rack/nulllogger.rb +21 -2
- data/lib/rack/request.rb +38 -24
- data/lib/rack/response.rb +5 -0
- data/lib/rack/sendfile.rb +10 -5
- data/lib/rack/server.rb +45 -17
- data/lib/rack/session/abstract/id.rb +7 -6
- data/lib/rack/session/cookie.rb +17 -7
- data/lib/rack/session/memcache.rb +4 -4
- data/lib/rack/session/pool.rb +3 -6
- data/lib/rack/showexceptions.rb +20 -11
- data/lib/rack/showstatus.rb +1 -1
- data/lib/rack/static.rb +27 -30
- data/lib/rack/tempfile_reaper.rb +22 -0
- data/lib/rack/urlmap.rb +17 -3
- data/lib/rack/utils.rb +78 -47
- data/lib/rack/utils/okjson.rb +90 -91
- data/rack.gemspec +3 -3
- data/test/multipart/filename_and_no_name +6 -0
- data/test/multipart/invalid_character +6 -0
- data/test/spec_builder.rb +13 -4
- data/test/spec_chunked.rb +16 -0
- data/test/spec_commonlogger.rb +36 -0
- data/test/spec_content_length.rb +3 -1
- data/test/spec_deflater.rb +283 -148
- data/test/spec_etag.rb +11 -2
- data/test/spec_file.rb +11 -3
- data/test/spec_head.rb +2 -0
- data/test/spec_lobster.rb +1 -1
- data/test/spec_mock.rb +8 -0
- data/test/spec_multipart.rb +111 -49
- data/test/spec_request.rb +109 -25
- data/test/spec_response.rb +30 -0
- data/test/spec_server.rb +20 -5
- data/test/spec_session_cookie.rb +45 -2
- data/test/spec_session_memcache.rb +1 -1
- data/test/spec_showexceptions.rb +29 -36
- data/test/spec_showstatus.rb +19 -0
- data/test/spec_tempfile_reaper.rb +63 -0
- data/test/spec_urlmap.rb +23 -0
- data/test/spec_utils.rb +60 -10
- data/test/spec_webrick.rb +41 -0
- metadata +12 -9
- data/test/cgi/lighttpd.errors +0 -1
- data/test/multipart/three_files_three_fields +0 -31
data/lib/rack/utils/okjson.rb
CHANGED
@@ -21,14 +21,16 @@
|
|
21
21
|
# THE SOFTWARE.
|
22
22
|
|
23
23
|
# See https://github.com/kr/okjson for updates.
|
24
|
+
# Imported from the above repo @ d4e8643ad92e14b37d11326855499c7e4108ed17
|
25
|
+
# Namespace modified for vendoring under Rack::Utils
|
24
26
|
|
25
27
|
require 'stringio'
|
26
28
|
|
27
29
|
# Some parts adapted from
|
28
|
-
#
|
29
|
-
#
|
30
|
+
# http://golang.org/src/pkg/json/decode.go and
|
31
|
+
# http://golang.org/src/pkg/utf8/utf8.go
|
30
32
|
module Rack::Utils::OkJson
|
31
|
-
Upstream = '
|
33
|
+
Upstream = 'LTD7LBKLZWFF7OZK'
|
32
34
|
extend self
|
33
35
|
|
34
36
|
|
@@ -50,49 +52,12 @@ module Rack::Utils::OkJson
|
|
50
52
|
end
|
51
53
|
|
52
54
|
|
53
|
-
# Encodes x into a json text. It may contain only
|
54
|
-
# Array, Hash, String, Numeric, true, false, nil.
|
55
|
-
# (Note, this list excludes Symbol.)
|
56
|
-
# X itself must be an Array or a Hash.
|
57
|
-
# No other value can be encoded, and an error will
|
58
|
-
# be raised if x contains any other value, such as
|
59
|
-
# Nan, Infinity, Symbol, and Proc, or if a Hash key
|
60
|
-
# is not a String.
|
61
|
-
# Strings contained in x must be valid UTF-8.
|
62
|
-
def encode(x)
|
63
|
-
case x
|
64
|
-
when Hash then objenc(x)
|
65
|
-
when Array then arrenc(x)
|
66
|
-
else
|
67
|
-
raise Error, 'root value must be an Array or a Hash'
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
|
72
|
-
def valenc(x)
|
73
|
-
case x
|
74
|
-
when Hash then objenc(x)
|
75
|
-
when Array then arrenc(x)
|
76
|
-
when String then strenc(x)
|
77
|
-
when Numeric then numenc(x)
|
78
|
-
when true then "true"
|
79
|
-
when false then "false"
|
80
|
-
when nil then "null"
|
81
|
-
else
|
82
|
-
raise Error, "cannot encode #{x.class}: #{x.inspect}"
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
|
87
|
-
private
|
88
|
-
|
89
|
-
|
90
55
|
# Parses a "json text" in the sense of RFC 4627.
|
91
56
|
# Returns the parsed value and any trailing tokens.
|
92
57
|
# Note: this is almost the same as valparse,
|
93
58
|
# except that it does not accept atomic values.
|
94
59
|
def textparse(ts)
|
95
|
-
if ts.length
|
60
|
+
if ts.length < 0
|
96
61
|
raise Error, 'empty'
|
97
62
|
end
|
98
63
|
|
@@ -109,7 +74,7 @@ private
|
|
109
74
|
# Parses a "value" in the sense of RFC 4627.
|
110
75
|
# Returns the parsed value and any trailing tokens.
|
111
76
|
def valparse(ts)
|
112
|
-
if ts.length
|
77
|
+
if ts.length < 0
|
113
78
|
raise Error, 'empty'
|
114
79
|
end
|
115
80
|
|
@@ -238,19 +203,21 @@ private
|
|
238
203
|
# it is the lexeme.
|
239
204
|
def tok(s)
|
240
205
|
case s[0]
|
241
|
-
when ?{
|
242
|
-
when ?}
|
243
|
-
when ?:
|
244
|
-
when ?,
|
245
|
-
when ?[
|
246
|
-
when ?]
|
247
|
-
when ?n
|
248
|
-
when ?t
|
249
|
-
when ?f
|
250
|
-
when ?"
|
251
|
-
when Spc
|
252
|
-
|
253
|
-
|
206
|
+
when ?{ then ['{', s[0,1], s[0,1]]
|
207
|
+
when ?} then ['}', s[0,1], s[0,1]]
|
208
|
+
when ?: then [':', s[0,1], s[0,1]]
|
209
|
+
when ?, then [',', s[0,1], s[0,1]]
|
210
|
+
when ?[ then ['[', s[0,1], s[0,1]]
|
211
|
+
when ?] then [']', s[0,1], s[0,1]]
|
212
|
+
when ?n then nulltok(s)
|
213
|
+
when ?t then truetok(s)
|
214
|
+
when ?f then falsetok(s)
|
215
|
+
when ?" then strtok(s)
|
216
|
+
when Spc then [:space, s[0,1], s[0,1]]
|
217
|
+
when ?\t then [:space, s[0,1], s[0,1]]
|
218
|
+
when ?\n then [:space, s[0,1], s[0,1]]
|
219
|
+
when ?\r then [:space, s[0,1], s[0,1]]
|
220
|
+
else numtok(s)
|
254
221
|
end
|
255
222
|
end
|
256
223
|
|
@@ -263,12 +230,12 @@ private
|
|
263
230
|
def numtok(s)
|
264
231
|
m = /-?([1-9][0-9]+|[0-9])([.][0-9]+)?([eE][+-]?[0-9]+)?/.match(s)
|
265
232
|
if m && m.begin(0) == 0
|
266
|
-
if
|
267
|
-
[:val, m[0], Integer(m[
|
233
|
+
if m[3] && !m[2]
|
234
|
+
[:val, m[0], Integer(m[1])*(10**Integer(m[3][1..-1]))]
|
268
235
|
elsif m[2]
|
269
236
|
[:val, m[0], Float(m[0])]
|
270
237
|
else
|
271
|
-
[:val, m[0], Integer(m[
|
238
|
+
[:val, m[0], Integer(m[0])]
|
272
239
|
end
|
273
240
|
else
|
274
241
|
[]
|
@@ -300,14 +267,17 @@ private
|
|
300
267
|
def unquote(q)
|
301
268
|
q = q[1...-1]
|
302
269
|
a = q.dup # allocate a big enough string
|
270
|
+
rubydoesenc = false
|
303
271
|
# In ruby >= 1.9, a[w] is a codepoint, not a byte.
|
304
|
-
if
|
272
|
+
if a.class.method_defined?(:force_encoding)
|
305
273
|
a.force_encoding('UTF-8')
|
274
|
+
rubydoesenc = true
|
306
275
|
end
|
307
276
|
r, w = 0, 0
|
308
277
|
while r < q.length
|
309
278
|
c = q[r]
|
310
|
-
|
279
|
+
case true
|
280
|
+
when c == ?\\
|
311
281
|
r += 1
|
312
282
|
if r >= q.length
|
313
283
|
raise Error, "string literal ends with a \"\\\": \"#{q}\""
|
@@ -340,7 +310,7 @@ private
|
|
340
310
|
end
|
341
311
|
end
|
342
312
|
end
|
343
|
-
if rubydoesenc
|
313
|
+
if rubydoesenc
|
344
314
|
a[w] = '' << uchar
|
345
315
|
w += 1
|
346
316
|
else
|
@@ -349,7 +319,7 @@ private
|
|
349
319
|
else
|
350
320
|
raise Error, "invalid escape char #{q[r]} in \"#{q}\""
|
351
321
|
end
|
352
|
-
|
322
|
+
when c == ?", c < Spc
|
353
323
|
raise Error, "invalid character in string literal \"#{q}\""
|
354
324
|
else
|
355
325
|
# Copy anything else byte-for-byte.
|
@@ -370,14 +340,15 @@ private
|
|
370
340
|
# bytes in string a at position i.
|
371
341
|
# Returns the number of bytes written.
|
372
342
|
def ucharenc(a, i, u)
|
373
|
-
|
343
|
+
case true
|
344
|
+
when u <= Uchar1max
|
374
345
|
a[i] = (u & 0xff).chr
|
375
346
|
1
|
376
|
-
|
347
|
+
when u <= Uchar2max
|
377
348
|
a[i+0] = (Utag2 | ((u>>6)&0xff)).chr
|
378
349
|
a[i+1] = (Utagx | (u&Umaskx)).chr
|
379
350
|
2
|
380
|
-
|
351
|
+
when u <= Uchar3max
|
381
352
|
a[i+0] = (Utag3 | ((u>>12)&0xff)).chr
|
382
353
|
a[i+1] = (Utagx | ((u>>6)&Umaskx)).chr
|
383
354
|
a[i+2] = (Utagx | (u&Umaskx)).chr
|
@@ -414,15 +385,50 @@ private
|
|
414
385
|
|
415
386
|
|
416
387
|
def nibble(c)
|
417
|
-
|
418
|
-
|
419
|
-
|
388
|
+
case true
|
389
|
+
when ?0 <= c && c <= ?9 then c.ord - ?0.ord
|
390
|
+
when ?a <= c && c <= ?z then c.ord - ?a.ord + 10
|
391
|
+
when ?A <= c && c <= ?Z then c.ord - ?A.ord + 10
|
420
392
|
else
|
421
393
|
raise Error, "invalid hex code #{c}"
|
422
394
|
end
|
423
395
|
end
|
424
396
|
|
425
397
|
|
398
|
+
# Encodes x into a json text. It may contain only
|
399
|
+
# Array, Hash, String, Numeric, true, false, nil.
|
400
|
+
# (Note, this list excludes Symbol.)
|
401
|
+
# X itself must be an Array or a Hash.
|
402
|
+
# No other value can be encoded, and an error will
|
403
|
+
# be raised if x contains any other value, such as
|
404
|
+
# Nan, Infinity, Symbol, and Proc, or if a Hash key
|
405
|
+
# is not a String.
|
406
|
+
# Strings contained in x must be valid UTF-8.
|
407
|
+
def encode(x)
|
408
|
+
case x
|
409
|
+
when Hash then objenc(x)
|
410
|
+
when Array then arrenc(x)
|
411
|
+
else
|
412
|
+
raise Error, 'root value must be an Array or a Hash'
|
413
|
+
end
|
414
|
+
end
|
415
|
+
|
416
|
+
|
417
|
+
def valenc(x)
|
418
|
+
case x
|
419
|
+
when Hash then objenc(x)
|
420
|
+
when Array then arrenc(x)
|
421
|
+
when String then strenc(x)
|
422
|
+
when Numeric then numenc(x)
|
423
|
+
when true then "true"
|
424
|
+
when false then "false"
|
425
|
+
when nil then "null"
|
426
|
+
else
|
427
|
+
raise Error, "cannot encode #{x.class}: #{x.inspect}"
|
428
|
+
end
|
429
|
+
end
|
430
|
+
|
431
|
+
|
426
432
|
def objenc(x)
|
427
433
|
'{' + x.map{|k,v| keyenc(k) + ':' + valenc(v)}.join(',') + '}'
|
428
434
|
end
|
@@ -447,6 +453,9 @@ private
|
|
447
453
|
t.putc(?")
|
448
454
|
r = 0
|
449
455
|
|
456
|
+
# In ruby >= 1.9, s[r] is a codepoint, not a byte.
|
457
|
+
rubydoesenc = s.class.method_defined?(:encoding)
|
458
|
+
|
450
459
|
while r < s.length
|
451
460
|
case s[r]
|
452
461
|
when ?" then t.print('\\"')
|
@@ -458,20 +467,15 @@ private
|
|
458
467
|
when ?\t then t.print('\\t')
|
459
468
|
else
|
460
469
|
c = s[r]
|
461
|
-
|
462
|
-
|
470
|
+
case true
|
471
|
+
when rubydoesenc
|
463
472
|
begin
|
464
|
-
|
465
|
-
if c.ord < Spc.ord
|
466
|
-
c = "\\u%04x" % [c.ord]
|
467
|
-
end
|
473
|
+
c.ord # will raise an error if c is invalid UTF-8
|
468
474
|
t.write(c)
|
469
475
|
rescue
|
470
476
|
t.write(Ustrerr)
|
471
477
|
end
|
472
|
-
|
473
|
-
t.write("\\u%04x" % c)
|
474
|
-
elsif Spc <= c && c <= ?~
|
478
|
+
when Spc <= c && c <= ?~
|
475
479
|
t.putc(c)
|
476
480
|
else
|
477
481
|
n = ucharcopy(t, s, r) # ensure valid UTF-8 output
|
@@ -563,11 +567,6 @@ private
|
|
563
567
|
end
|
564
568
|
|
565
569
|
|
566
|
-
def rubydoesenc?
|
567
|
-
::String.method_defined?(:force_encoding)
|
568
|
-
end
|
569
|
-
|
570
|
-
|
571
570
|
class Utf8Error < ::StandardError
|
572
571
|
end
|
573
572
|
|
@@ -576,15 +575,15 @@ private
|
|
576
575
|
end
|
577
576
|
|
578
577
|
|
579
|
-
Utagx =
|
580
|
-
Utag2 =
|
581
|
-
Utag3 =
|
582
|
-
Utag4 =
|
583
|
-
Utag5 =
|
584
|
-
Umaskx =
|
585
|
-
Umask2 =
|
586
|
-
Umask3 =
|
587
|
-
Umask4 =
|
578
|
+
Utagx = 0x80 # 1000 0000
|
579
|
+
Utag2 = 0xc0 # 1100 0000
|
580
|
+
Utag3 = 0xe0 # 1110 0000
|
581
|
+
Utag4 = 0xf0 # 1111 0000
|
582
|
+
Utag5 = 0xF8 # 1111 1000
|
583
|
+
Umaskx = 0x3f # 0011 1111
|
584
|
+
Umask2 = 0x1f # 0001 1111
|
585
|
+
Umask3 = 0x0f # 0000 1111
|
586
|
+
Umask4 = 0x07 # 0000 0111
|
588
587
|
Uchar1max = (1<<7) - 1
|
589
588
|
Uchar2max = (1<<11) - 1
|
590
589
|
Uchar3max = (1<<16) - 1
|
data/rack.gemspec
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = "rack"
|
3
|
-
s.version = "1.
|
3
|
+
s.version = "1.6.0.beta"
|
4
4
|
s.platform = Gem::Platform::RUBY
|
5
5
|
s.summary = "a modular Ruby webserver interface"
|
6
6
|
s.license = "MIT"
|
@@ -12,7 +12,7 @@ the simplest way possible, it unifies and distills the API for web
|
|
12
12
|
servers, web frameworks, and software in between (the so-called
|
13
13
|
middleware) into a single method call.
|
14
14
|
|
15
|
-
Also see http://rack.github.
|
15
|
+
Also see http://rack.github.io/.
|
16
16
|
EOF
|
17
17
|
|
18
18
|
s.files = Dir['{bin/*,contrib/*,example/*,lib/**/*,test/**/*}'] +
|
@@ -25,7 +25,7 @@ EOF
|
|
25
25
|
|
26
26
|
s.author = 'Christian Neukirchen'
|
27
27
|
s.email = 'chneukirchen@gmail.com'
|
28
|
-
s.homepage = 'http://rack.github.
|
28
|
+
s.homepage = 'http://rack.github.io/'
|
29
29
|
s.rubyforge_project = 'rack'
|
30
30
|
|
31
31
|
s.add_development_dependency 'bacon'
|
data/test/spec_builder.rb
CHANGED
@@ -130,6 +130,17 @@ describe Rack::Builder do
|
|
130
130
|
Rack::MockRequest.new(app).get("/foo").should.be.server_error
|
131
131
|
end
|
132
132
|
|
133
|
+
it "yields the generated app to a block for warmup" do
|
134
|
+
warmed_up_app = nil
|
135
|
+
|
136
|
+
app = Rack::Builder.new do
|
137
|
+
warmup { |a| warmed_up_app = a }
|
138
|
+
run lambda { |env| [200, {}, []] }
|
139
|
+
end.to_app
|
140
|
+
|
141
|
+
warmed_up_app.should.equal app
|
142
|
+
end
|
143
|
+
|
133
144
|
should "initialize apps once" do
|
134
145
|
app = builder do
|
135
146
|
class AppClass
|
@@ -180,8 +191,7 @@ describe Rack::Builder do
|
|
180
191
|
end
|
181
192
|
|
182
193
|
it "removes __END__ before evaluating app" do
|
183
|
-
app,
|
184
|
-
options = nil # ignored, prevents warning
|
194
|
+
app, _ = Rack::Builder.parse_file config_file('end.ru')
|
185
195
|
Rack::MockRequest.new(app).get("/").body.to_s.should.equal 'OK'
|
186
196
|
end
|
187
197
|
|
@@ -199,8 +209,7 @@ describe Rack::Builder do
|
|
199
209
|
end
|
200
210
|
|
201
211
|
it "sets __LINE__ correctly" do
|
202
|
-
app,
|
203
|
-
options = nil # ignored, prevents warning
|
212
|
+
app, _ = Rack::Builder.parse_file config_file('line.ru')
|
204
213
|
Rack::MockRequest.new(app).get("/").body.to_s.should.equal '1'
|
205
214
|
end
|
206
215
|
end
|
data/test/spec_chunked.rb
CHANGED
@@ -64,6 +64,22 @@ describe Rack::Chunked do
|
|
64
64
|
body.join.should.equal 'Hello World!'
|
65
65
|
end
|
66
66
|
|
67
|
+
should 'not modify response when client is ancient, pre-HTTP/1.0' do
|
68
|
+
app = lambda { |env| [200, {"Content-Type" => "text/plain"}, ['Hello', ' ', 'World!']] }
|
69
|
+
check = lambda do
|
70
|
+
status, headers, body = chunked(app).call(@env.dup)
|
71
|
+
status.should.equal 200
|
72
|
+
headers.should.not.include 'Transfer-Encoding'
|
73
|
+
body.join.should.equal 'Hello World!'
|
74
|
+
end
|
75
|
+
|
76
|
+
@env.delete('HTTP_VERSION') # unicorn will do this on pre-HTTP/1.0 requests
|
77
|
+
check.call
|
78
|
+
|
79
|
+
@env['HTTP_VERSION'] = 'HTTP/0.9' # not sure if this happens in practice
|
80
|
+
check.call
|
81
|
+
end
|
82
|
+
|
67
83
|
should 'not modify response when Transfer-Encoding header already present' do
|
68
84
|
app = lambda { |env|
|
69
85
|
[200, {"Content-Type" => "text/plain", 'Transfer-Encoding' => 'identity'}, ['Hello', ' ', 'World!']]
|
data/test/spec_commonlogger.rb
CHANGED
@@ -2,6 +2,8 @@ require 'rack/commonlogger'
|
|
2
2
|
require 'rack/lint'
|
3
3
|
require 'rack/mock'
|
4
4
|
|
5
|
+
require 'logger'
|
6
|
+
|
5
7
|
describe Rack::CommonLogger do
|
6
8
|
obj = 'foobar'
|
7
9
|
length = obj.size
|
@@ -33,6 +35,14 @@ describe Rack::CommonLogger do
|
|
33
35
|
log.string.should =~ /"GET \/ " 200 #{length} /
|
34
36
|
end
|
35
37
|
|
38
|
+
should "work with standartd library logger" do
|
39
|
+
logdev = StringIO.new
|
40
|
+
log = Logger.new(logdev)
|
41
|
+
Rack::MockRequest.new(Rack::CommonLogger.new(app, log)).get("/")
|
42
|
+
|
43
|
+
logdev.string.should =~ /"GET \/ " 200 #{length} /
|
44
|
+
end
|
45
|
+
|
36
46
|
should "log - content length if header is missing" do
|
37
47
|
res = Rack::MockRequest.new(Rack::CommonLogger.new(app_without_length)).get("/")
|
38
48
|
|
@@ -47,6 +57,32 @@ describe Rack::CommonLogger do
|
|
47
57
|
res.errors.should =~ /"GET \/ " 200 - /
|
48
58
|
end
|
49
59
|
|
60
|
+
def with_mock_time(t = 0)
|
61
|
+
mc = class <<Time; self; end
|
62
|
+
mc.send :alias_method, :old_now, :now
|
63
|
+
mc.send :define_method, :now do
|
64
|
+
at(t)
|
65
|
+
end
|
66
|
+
yield
|
67
|
+
ensure
|
68
|
+
mc.send :alias_method, :now, :old_now
|
69
|
+
end
|
70
|
+
|
71
|
+
should "log in common log format" do
|
72
|
+
log = StringIO.new
|
73
|
+
with_mock_time do
|
74
|
+
Rack::MockRequest.new(Rack::CommonLogger.new(app, log)).get("/")
|
75
|
+
end
|
76
|
+
|
77
|
+
md = /- - - \[([^\]]+)\] "(\w+) \/ " (\d{3}) \d+ ([\d\.]+)/.match(log.string)
|
78
|
+
md.should.not.equal nil
|
79
|
+
time, method, status, duration = *md.captures
|
80
|
+
time.should.equal Time.at(0).strftime("%d/%b/%Y:%H:%M:%S %z")
|
81
|
+
method.should.equal "GET"
|
82
|
+
status.should.equal "200"
|
83
|
+
(0..1).should.include?(duration.to_f)
|
84
|
+
end
|
85
|
+
|
50
86
|
def length
|
51
87
|
123
|
52
88
|
end
|