http_tools 0.4.3 → 0.4.4
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +2 -2
- data/bench/parser/large_response_bench.rb +6 -5
- data/bench/parser/request_bench.rb +7 -32
- data/bench/parser/response_bench.rb +5 -5
- data/bench/transfer_encoding_chunked_bench.rb +6 -5
- data/example/http_server.rb +23 -24
- data/lib/http_tools.rb +6 -4
- data/lib/http_tools/parser.rb +45 -18
- data/profile/parser/request_profile.rb +5 -13
- data/test/cover.rb +8 -15
- data/test/parser/request_test.rb +132 -14
- data/test/parser/response_test.rb +149 -5
- metadata +41 -22
data/README.rdoc
CHANGED
@@ -13,8 +13,8 @@ should run across all Ruby implementations compatible with 1.8 or later, and
|
|
13
13
|
install in environments without a compiler available.
|
14
14
|
|
15
15
|
Tests are currently run on travis-ci[http://travis-ci.org/matsadler/http_tools]
|
16
|
-
against Ruby 1.8.
|
17
|
-
Enterprise Edition. Additionally tests are run against MacRuby
|
16
|
+
against Ruby 1.8.7, 1.9.2, 1.9.3, JRuby, Rubinius, and Ruby
|
17
|
+
Enterprise Edition. Additionally tests are run against 1.8.6 and MacRuby
|
18
18
|
|
19
19
|
Performance tuning is mainly aimed at Ruby 1.9, with Ruby 1.8 and JRuby taken in
|
20
20
|
to consideration. JRuby is generally fastest.
|
@@ -1,7 +1,8 @@
|
|
1
|
-
|
2
|
-
require base + '/http_tools'
|
1
|
+
require File.expand_path('../../../lib/http_tools', __FILE__)
|
3
2
|
require 'benchmark'
|
4
3
|
|
4
|
+
repeats = 200
|
5
|
+
|
5
6
|
Benchmark.bm(26) do |x|
|
6
7
|
body = "x" * 1024 * 1024
|
7
8
|
chunks = []
|
@@ -9,7 +10,7 @@ Benchmark.bm(26) do |x|
|
|
9
10
|
|
10
11
|
header = "HTTP/1.1 200 OK\r\nDate: Mon, 06 Jun 2011 14:55:51 GMT\r\nServer: Apache/2.2.17 (Unix) mod_ssl/2.2.17 OpenSSL/0.9.8l DAV/2 mod_fastcgi/2.4.2\r\nLast-Modified: Mon, 06 Jun 2011 14:55:49 GMT\r\nETag: \"3f18045-400-4a50c4c87c740\"\r\nAccept-Ranges: bytes\r\nContent-Length: #{body.length}\r\nContent-Type: text/plain\r\n\r\n"
|
11
12
|
x.report("content_length") do
|
12
|
-
|
13
|
+
repeats.times do
|
13
14
|
parser = HTTPTools::Parser.new
|
14
15
|
parser << header
|
15
16
|
chunks.each {|chunk| parser << chunk}
|
@@ -18,7 +19,7 @@ Benchmark.bm(26) do |x|
|
|
18
19
|
|
19
20
|
header = "HTTP/1.1 200 OK\r\nDate: Mon, 06 Jun 2011 14:55:51 GMT\r\nServer: Apache/2.2.17 (Unix) mod_ssl/2.2.17 OpenSSL/0.9.8l DAV/2 mod_fastcgi/2.4.2\r\nLast-Modified: Mon, 06 Jun 2011 14:55:49 GMT\r\nETag: \"3f18045-400-4a50c4c87c740\"\r\nAccept-Ranges: bytes\r\nConnection: close\r\nContent-Type: text/plain\r\n\r\n"
|
20
21
|
x.report("close") do
|
21
|
-
|
22
|
+
repeats.times do
|
22
23
|
parser = HTTPTools::Parser.new
|
23
24
|
parser << header
|
24
25
|
chunks.each {|chunk| parser << chunk}
|
@@ -30,7 +31,7 @@ Benchmark.bm(26) do |x|
|
|
30
31
|
chunks << nil
|
31
32
|
chunks.map!(&HTTPTools::Encoding.method(:transfer_encoding_chunked_encode))
|
32
33
|
x.report("chunked") do
|
33
|
-
|
34
|
+
repeats.times do
|
34
35
|
parser = HTTPTools::Parser.new
|
35
36
|
parser << header
|
36
37
|
chunks.each {|chunk| parser << chunk}
|
@@ -1,19 +1,19 @@
|
|
1
|
-
|
2
|
-
require base + '/http_tools'
|
1
|
+
require File.expand_path('../../../lib/http_tools', __FILE__)
|
3
2
|
require 'benchmark'
|
4
3
|
|
5
4
|
request = "GET / HTTP/1.1\r\nHost: example.com\r\nUser-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_8; en-gb) AppleWebKit/533.16 (KHTML, like Gecko) Version/5.0 Safari/533.16\r\nAccept: application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5\r\nAccept-Language: en-gb\r\nAccept-Encoding: gzip, deflate\r\nConnection: keep-alive\r\n\r\n"
|
5
|
+
repeats = 10_000
|
6
6
|
|
7
7
|
Benchmark.bm(41) do |x|
|
8
8
|
x.report("HTTPTools::Parser") do
|
9
|
-
|
9
|
+
repeats.times do
|
10
10
|
HTTPTools::Parser.new << request
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
14
|
x.report("HTTPTools::Parser (reset)") do
|
15
15
|
parser = HTTPTools::Parser.new
|
16
|
-
|
16
|
+
repeats.times do
|
17
17
|
parser << request
|
18
18
|
parser.reset
|
19
19
|
end
|
@@ -23,7 +23,7 @@ Benchmark.bm(41) do |x|
|
|
23
23
|
parser = HTTPTools::Parser.new
|
24
24
|
parser.on(:header) {}
|
25
25
|
parser.on(:finish) {}
|
26
|
-
|
26
|
+
repeats.times do
|
27
27
|
parser << request
|
28
28
|
parser.reset
|
29
29
|
end
|
@@ -31,35 +31,10 @@ Benchmark.bm(41) do |x|
|
|
31
31
|
|
32
32
|
x.report("HTTPTools::Parser (reset, with env)") do
|
33
33
|
parser = HTTPTools::Parser.new
|
34
|
-
|
34
|
+
repeats.times do
|
35
35
|
parser << request
|
36
36
|
parser.env
|
37
37
|
parser.reset
|
38
38
|
end
|
39
39
|
end
|
40
|
-
|
41
|
-
begin
|
42
|
-
require 'rubygems'
|
43
|
-
require 'http/parser'
|
44
|
-
x.report("Http::Parser") do
|
45
|
-
10_000.times do
|
46
|
-
parser = Http::Parser.new
|
47
|
-
parser.on_headers_complete = Proc.new {}
|
48
|
-
parser.on_message_complete = Proc.new {}
|
49
|
-
parser << request
|
50
|
-
end
|
51
|
-
end
|
52
|
-
rescue LoadError
|
53
|
-
end
|
54
|
-
|
55
|
-
begin
|
56
|
-
require 'rubygems'
|
57
|
-
require 'http11'
|
58
|
-
x.report("Mongrel::HttpParser") do
|
59
|
-
10_000.times do
|
60
|
-
Mongrel::HttpParser.new.execute({}, request.dup, 0)
|
61
|
-
end
|
62
|
-
end
|
63
|
-
rescue LoadError
|
64
|
-
end
|
65
|
-
end
|
40
|
+
end
|
@@ -1,19 +1,19 @@
|
|
1
|
-
|
2
|
-
require base + '/http_tools'
|
1
|
+
require File.expand_path('../../../lib/http_tools', __FILE__)
|
3
2
|
require 'benchmark'
|
4
3
|
|
5
4
|
response = "HTTP/1.1 200 OK\r\nServer: Apache/2.2.3 (CentOS)\r\nLast-Modified: Thu, 03 Jun 2010 17:40:12 GMT\r\nETag: \"4d2c-23e-48823b2cf3700\"\r\nAccept-Ranges: bytes\r\nContent-Type: text/html; charset=UTF-8\r\nConnection: Keep-Alive\r\nDate: Wed, 21 Jul 2010 16:26:04 GMT\r\nAge: 7985 \r\nContent-Length: 574\r\n\r\n<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\r\n<HTML>\r\n<HEAD>\r\n <META http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\r\n <TITLE>Example Web Page</TITLE>\r\n</HEAD> \r\n<body> \r\n<p>You have reached this web page by typing "example.com",\r\n"example.net",\r\n or "example.org" into your web browser.</p>\r\n<p>These domain names are reserved for use in documentation and are not available \r\n for registration. See <a href=\"http://www.rfc-editor.org/rfc/rfc2606.txt\">RFC \r\n 2606</a>, Section 3.</p>\r\n</BODY>\r\n</HTML>\r\n\r\n"
|
5
|
+
repeats = 10_000
|
6
6
|
|
7
7
|
Benchmark.bm(25) do |x|
|
8
8
|
x.report("HTTPTools::Parser") do
|
9
|
-
|
9
|
+
repeats.times do
|
10
10
|
HTTPTools::Parser.new << response
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
14
|
x.report("HTTPTools::Parser (reset)") do
|
15
15
|
parser = HTTPTools::Parser.new
|
16
|
-
|
16
|
+
repeats.times do
|
17
17
|
parser << response
|
18
18
|
parser.reset
|
19
19
|
end
|
@@ -23,7 +23,7 @@ Benchmark.bm(25) do |x|
|
|
23
23
|
require 'rubygems'
|
24
24
|
require 'http/parser'
|
25
25
|
x.report("Http::Parser") do
|
26
|
-
|
26
|
+
repeats.times do
|
27
27
|
parser = Http::Parser.new
|
28
28
|
parser << response
|
29
29
|
end
|
@@ -1,25 +1,26 @@
|
|
1
|
-
|
2
|
-
require base + '/http_tools'
|
1
|
+
require File.expand_path('../../lib/http_tools', __FILE__)
|
3
2
|
require 'benchmark'
|
4
3
|
|
4
|
+
repeats = 1_000
|
5
|
+
|
5
6
|
Benchmark.bm(36) do |x|
|
6
7
|
encoded = "1\r\na\r\n" * 100 + "0\r\n"
|
7
8
|
x.report("lots of very short chunks") do
|
8
|
-
|
9
|
+
repeats.times do
|
9
10
|
HTTPTools::Encoding.transfer_encoding_chunked_decode(encoded)
|
10
11
|
end
|
11
12
|
end
|
12
13
|
|
13
14
|
encoded = "16\r\n<h1>Hello world</h1>\r\n\r\n12\r\n<p>Lorem ipsum</p>\r\n" * 50 + "0\r\n"
|
14
15
|
x.report("slightly less slightly longer chunks") do
|
15
|
-
|
16
|
+
repeats.times do
|
16
17
|
HTTPTools::Encoding.transfer_encoding_chunked_decode(encoded)
|
17
18
|
end
|
18
19
|
end
|
19
20
|
|
20
21
|
encoded = "2710\r\n#{"a" * 10000}\r\n" * 2 + "0\r\n"
|
21
22
|
x.report("a couple of big chunks") do
|
22
|
-
|
23
|
+
repeats.times do
|
23
24
|
HTTPTools::Encoding.transfer_encoding_chunked_decode(encoded)
|
24
25
|
end
|
25
26
|
end
|
data/example/http_server.rb
CHANGED
@@ -3,19 +3,20 @@ require 'rubygems'
|
|
3
3
|
require 'http_tools'
|
4
4
|
|
5
5
|
module HTTP
|
6
|
+
|
7
|
+
# Basic Rack HTTP server.
|
8
|
+
#
|
9
|
+
# Usage:
|
10
|
+
#
|
11
|
+
# app = lambda {|env| [200, {"Content-Length" => "3"}, ["Hi\n"]]}
|
12
|
+
# HTTP::Server.run(app)
|
13
|
+
#
|
6
14
|
class Server
|
7
|
-
CONNECTION = "Connection".freeze
|
8
|
-
KEEP_ALIVE = "Keep-Alive".freeze
|
9
|
-
CLOSE = "close".freeze
|
10
|
-
ONE_ONE = "1.1".freeze
|
11
15
|
|
12
16
|
def initialize(app, options={})
|
13
|
-
host = options[:host] || options[:Host] || "0.0.0.0"
|
14
|
-
port = (options[:port] || options[:Port] || 9292).to_s
|
17
|
+
@host = options[:host] || options[:Host] || "0.0.0.0"
|
18
|
+
@port = (options[:port] || options[:Port] || 9292).to_s
|
15
19
|
@app = app
|
16
|
-
@instance_env = {"rack.multithread" => true}
|
17
|
-
@server = TCPServer.new(host, port)
|
18
|
-
@server.listen(1024)
|
19
20
|
end
|
20
21
|
|
21
22
|
def self.run(app, options={})
|
@@ -23,25 +24,23 @@ module HTTP
|
|
23
24
|
end
|
24
25
|
|
25
26
|
def listen
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
on_connection(socket)
|
30
|
-
rescue StandardError, LoadError, SyntaxError => e
|
31
|
-
STDERR.puts("#{e.class}: #{e.message} #{e.backtrace.join("\n")}")
|
32
|
-
end
|
33
|
-
end
|
27
|
+
server = TCPServer.new(@host, @port)
|
28
|
+
while socket = server.accept
|
29
|
+
Thread.new {on_connection(socket)}
|
34
30
|
end
|
35
31
|
end
|
36
32
|
|
37
33
|
private
|
34
|
+
|
38
35
|
def on_connection(socket)
|
39
36
|
parser = HTTPTools::Parser.new
|
40
37
|
|
41
38
|
parser.on(:finish) do
|
42
|
-
|
43
|
-
|
44
|
-
|
39
|
+
env = parser.env.merge!("rack.multithread" => true)
|
40
|
+
status, header, body = @app.call(env)
|
41
|
+
|
42
|
+
keep_alive = parser.header["Connection"] != "close"
|
43
|
+
header["Connection"] = keep_alive ? "Keep-Alive" : "close"
|
45
44
|
socket << HTTPTools::Builder.response(status, header)
|
46
45
|
body.each {|chunk| socket << chunk}
|
47
46
|
body.close if body.respond_to?(:close)
|
@@ -56,12 +55,12 @@ module HTTP
|
|
56
55
|
rescue EOFError
|
57
56
|
break
|
58
57
|
end until parser.finished?
|
58
|
+
|
59
|
+
rescue StandardError, LoadError, SyntaxError => e
|
60
|
+
STDERR.puts("#{e.class}: #{e.message} #{e.backtrace.join("\n")}")
|
61
|
+
ensure
|
59
62
|
socket.close
|
60
63
|
end
|
61
64
|
|
62
|
-
def keep_alive?(http_version, connection)
|
63
|
-
http_version == ONE_ONE && connection != CLOSE || connection == KEEP_ALIVE
|
64
|
-
end
|
65
|
-
|
66
65
|
end
|
67
66
|
end
|
data/lib/http_tools.rb
CHANGED
@@ -108,11 +108,13 @@ module HTTPTools
|
|
108
108
|
STATUS_DESCRIPTIONS.values.each {|val| val.freeze}
|
109
109
|
|
110
110
|
# :stopdoc: hide from rdoc as it makes a mess
|
111
|
-
STATUS_LINES =
|
112
|
-
|
113
|
-
|
114
|
-
|
111
|
+
STATUS_LINES = {}
|
112
|
+
STATUS_CODES.each do |name, code|
|
113
|
+
line = "#{code} #{STATUS_DESCRIPTIONS[code]}"
|
114
|
+
STATUS_LINES[name] = line
|
115
|
+
STATUS_LINES[code] = line
|
115
116
|
end
|
117
|
+
STATUS_LINES.freeze
|
116
118
|
# :startdoc:
|
117
119
|
|
118
120
|
METHODS = %W{GET POST HEAD PUT DELETE OPTIONS TRACE CONNECT}.freeze
|
data/lib/http_tools/parser.rb
CHANGED
@@ -297,7 +297,7 @@ module HTTPTools
|
|
297
297
|
alias on add_listener
|
298
298
|
|
299
299
|
def inspect # :nodoc:
|
300
|
-
super.sub(/ .*>$/, " #{posstr} #{state}>")
|
300
|
+
super.sub(/ .*>$/, " #{posstr(false)} #{state}>")
|
301
301
|
end
|
302
302
|
|
303
303
|
private
|
@@ -313,6 +313,7 @@ module HTTPTools
|
|
313
313
|
elsif @allow_html_without_header && @buffer.check(/\s*</i)
|
314
314
|
skip_header
|
315
315
|
else
|
316
|
+
@buffer.skip(/H(T(TP?)?)?/i) || @buffer.skip(/[a-z]+/i)
|
316
317
|
raise ParseError.new("Protocol or method not recognised at " + posstr)
|
317
318
|
end
|
318
319
|
end
|
@@ -325,9 +326,10 @@ module HTTPTools
|
|
325
326
|
@query_string = @path_info.slice!(/\?[a-z0-9;\/?:@&=+$,%_.!~*')(-]*/i)
|
326
327
|
@query_string ? @query_string[0] = EMPTY : @query_string = ""
|
327
328
|
request_http_version
|
328
|
-
elsif @buffer.check(/[a-z0-9;\/?:@&=+$,%_.!~*')(
|
329
|
+
elsif @buffer.check(/[a-z0-9;\/?:@&=+$,%_.!~*')(-]+\Z/i)
|
329
330
|
:uri
|
330
331
|
else
|
332
|
+
@buffer.skip(/[a-z0-9;\/?:@&=+$,%_.!~*')(-]+/i)
|
331
333
|
raise ParseError.new("URI or path not recognised at " + posstr)
|
332
334
|
end
|
333
335
|
end
|
@@ -343,6 +345,7 @@ module HTTPTools
|
|
343
345
|
@buffer.check(/ (H(T(T(P(\/(\d+(\.(\d+\r?)?)?)?)?)?)?)?)?\Z/i)
|
344
346
|
:request_http_version
|
345
347
|
else
|
348
|
+
@buffer.skip(/ (H(T(T(P(\/(\d+(\.(\d+\r?)?)?)?)?)?)?)?)?/i)
|
346
349
|
raise ParseError.new("Invalid version specifier at " + posstr)
|
347
350
|
end
|
348
351
|
end
|
@@ -356,6 +359,7 @@ module HTTPTools
|
|
356
359
|
@buffer.check(/H(T(T(P(\/(\d+(\.(\d+\r?)?)?)?)?)?)?)?\Z/i)
|
357
360
|
:response_http_version
|
358
361
|
else
|
362
|
+
@buffer.skip(/H(T(T(P(\/(\d+(\.(\d+\r?)?)?)?)?)?)?)?/i)
|
359
363
|
raise ParseError.new("Invalid version specifier at " + posstr)
|
360
364
|
end
|
361
365
|
end
|
@@ -379,6 +383,7 @@ module HTTPTools
|
|
379
383
|
@buffer.check(/\d(\d(\d( ([^\x00-\x1f\x7f]+\r?)?)?)?)?\Z/i)
|
380
384
|
:status
|
381
385
|
else
|
386
|
+
@buffer.skip(/\d(\d(\d( ([^\x00-\x1f\x7f]+\r?)?)?)?)?/i)
|
382
387
|
raise ParseError.new("Invalid status line at " + posstr)
|
383
388
|
end
|
384
389
|
end
|
@@ -397,6 +402,9 @@ module HTTPTools
|
|
397
402
|
elsif @last_key = @buffer.scan(/[ -9;-~]+:(?=[^ ])/i)
|
398
403
|
@last_key.chomp!(COLON)
|
399
404
|
value
|
405
|
+
elsif @request_method
|
406
|
+
@buffer.skip(/[ -9;-~]+/i)
|
407
|
+
raise ParseError.new("Illegal character in field name at " + posstr)
|
400
408
|
else
|
401
409
|
skip_bad_header
|
402
410
|
end
|
@@ -408,6 +416,7 @@ module HTTPTools
|
|
408
416
|
elsif @buffer.check(/[^\x00\n\x7f]+\Z/)
|
409
417
|
:skip_bad_header
|
410
418
|
else
|
419
|
+
@buffer.skip(/[ -9;-~]+/i)
|
411
420
|
raise ParseError.new("Illegal character in field name at " + posstr)
|
412
421
|
end
|
413
422
|
end
|
@@ -430,20 +439,24 @@ module HTTPTools
|
|
430
439
|
elsif @buffer.eos? || @buffer.check(/[^\x00\n\x7f]+\Z/i)
|
431
440
|
:value
|
432
441
|
else
|
442
|
+
@buffer.skip(/[^\x00\n\x7f]+/i)
|
433
443
|
raise ParseError.new("Illegal character in field body at " + posstr)
|
434
444
|
end
|
435
445
|
end
|
436
446
|
|
437
447
|
def value_extention
|
438
|
-
if @buffer.check(/[^ \t]/)
|
448
|
+
if @buffer.check(/[^ \t]/i)
|
439
449
|
key_or_newline
|
440
|
-
elsif value_extra = @buffer.scan(/[ \t]+[^\x00\n\x7f]*\n/)
|
441
|
-
value_extra.sub!(/^[ \t]
|
450
|
+
elsif value_extra = @buffer.scan(/[ \t]+[^\x00\n\x7f]*\n/i)
|
451
|
+
value_extra.sub!(/^[ \t]+/i, SPACE)
|
442
452
|
value_extra.chop!
|
443
453
|
(@header[@last_key] << value_extra).strip!
|
444
454
|
value_extention
|
445
|
-
|
455
|
+
elsif @buffer.eos? || @buffer.check(/[ \t]+[^\x00\n\x7f]*\Z/i)
|
446
456
|
:value_extention
|
457
|
+
else
|
458
|
+
@buffer.skip(/[ \t]+[^\x00\n\x7f]*/i)
|
459
|
+
raise ParseError.new("Illegal character in field body at " + posstr)
|
447
460
|
end
|
448
461
|
end
|
449
462
|
|
@@ -544,6 +557,7 @@ module HTTPTools
|
|
544
557
|
@last_key.chomp!(COLON)
|
545
558
|
trailer_value
|
546
559
|
else
|
560
|
+
@buffer.skip(/[ -9;-~]+/i)
|
547
561
|
raise ParseError.new("Illegal character in field name at " + posstr)
|
548
562
|
end
|
549
563
|
end
|
@@ -561,20 +575,24 @@ module HTTPTools
|
|
561
575
|
elsif @buffer.eos? || @buffer.check(/[^\x00\n\x7f]+\Z/i)
|
562
576
|
:trailer_value
|
563
577
|
else
|
578
|
+
@buffer.skip(/[^\x00\n\x7f]+/i)
|
564
579
|
raise ParseError.new("Illegal character in field body at " + posstr)
|
565
580
|
end
|
566
581
|
end
|
567
582
|
|
568
583
|
def trailer_value_extention
|
569
|
-
if @buffer.check(/[^ \t]/)
|
584
|
+
if @buffer.check(/[^ \t]/i)
|
570
585
|
trailer_key_or_newline
|
571
|
-
elsif value_extra = @buffer.scan(/[ \t]+[^\x00\n\x7f]*\n/)
|
572
|
-
value_extra.sub!(/^[ \t]
|
586
|
+
elsif value_extra = @buffer.scan(/[ \t]+[^\x00\n\x7f]*\n/i)
|
587
|
+
value_extra.sub!(/^[ \t]+/i, SPACE)
|
573
588
|
value_extra.chop!
|
574
589
|
(@trailer[@last_key] << value_extra).strip!
|
575
590
|
trailer_value_extention
|
576
|
-
|
591
|
+
elsif @buffer.eos? || @buffer.check(/[ \t]+[^\x00\n\x7f]*\Z/i)
|
577
592
|
:trailer_value_extention
|
593
|
+
else
|
594
|
+
@buffer.skip(/[ \t]+[^\x00\n\x7f]*/i)
|
595
|
+
raise ParseError.new("Illegal character in field body at " + posstr)
|
578
596
|
end
|
579
597
|
end
|
580
598
|
|
@@ -603,19 +621,28 @@ module HTTPTools
|
|
603
621
|
end
|
604
622
|
|
605
623
|
def line_char(string, position)
|
606
|
-
line_count =
|
624
|
+
line_count = 0
|
607
625
|
char_count = 0
|
608
626
|
string.each_line do |line|
|
609
|
-
break if line.length + char_count
|
627
|
+
break if line.length + char_count >= position
|
610
628
|
line_count += 1
|
611
629
|
char_count += line.length
|
612
630
|
end
|
613
|
-
[line_count, position
|
614
|
-
end
|
615
|
-
|
616
|
-
def posstr
|
617
|
-
|
618
|
-
"line #{
|
631
|
+
[line_count, position - char_count]
|
632
|
+
end
|
633
|
+
|
634
|
+
def posstr(visual=true)
|
635
|
+
line_num, char_num = line_char(@buffer.string, @buffer.pos)
|
636
|
+
out = "line #{line_num + 1}, char #{char_num + 1}"
|
637
|
+
return out unless visual && @buffer.string.respond_to?(:lines)
|
638
|
+
line = ""
|
639
|
+
pointer = nil
|
640
|
+
@buffer.string.lines.to_a[line_num].chars.each_with_index.map do |char, i|
|
641
|
+
line << char.dump.gsub(/(^"|"$)/, "")
|
642
|
+
pointer = "#{" " * (line.length - 1)}^" if i == char_num
|
643
|
+
end
|
644
|
+
pointer ||= " " * line.length + "^"
|
645
|
+
[out, "", line, pointer].join("\n")
|
619
646
|
end
|
620
647
|
|
621
648
|
end
|
@@ -1,20 +1,12 @@
|
|
1
1
|
base = File.expand_path(File.dirname(__FILE__) + '/../../lib')
|
2
2
|
require base + '/http_tools'
|
3
3
|
require 'rubygems'
|
4
|
+
require 'ruby-prof'
|
4
5
|
|
5
6
|
request = "GET / HTTP/1.1\r\nHost: example.com\r\nUser-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_8; en-gb) AppleWebKit/533.16 (KHTML, like Gecko) Version/5.0 Safari/533.16\r\nAccept: application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5\r\nAccept-Language: en-gb\r\nAccept-Encoding: gzip, deflate\r\nConnection: keep-alive\r\n\r\n"
|
6
7
|
parser = HTTPTools::Parser.new
|
7
8
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
# RubyProf::FlatPrinter.new(result).print(STDOUT, 0)
|
13
|
-
|
14
|
-
require 'perftools'
|
15
|
-
PerfTools::CpuProfiler.start("/tmp/http_tools_parser_request_profile") do
|
16
|
-
100_000.times do
|
17
|
-
parser << request
|
18
|
-
parser.reset
|
19
|
-
end
|
20
|
-
end
|
9
|
+
result = RubyProf.profile do
|
10
|
+
parser << request
|
11
|
+
end
|
12
|
+
RubyProf::FlatPrinter.new(result).print(STDOUT, 0)
|
data/test/cover.rb
CHANGED
@@ -1,28 +1,21 @@
|
|
1
1
|
require 'coverage' # >= ruby 1.9 only
|
2
2
|
|
3
|
+
testing = Dir[File.expand_path("../../lib/**/*.rb", __FILE__)]
|
4
|
+
|
3
5
|
at_exit do
|
4
|
-
testing = Dir[File.expand_path("../../lib/**/*.rb", __FILE__)]
|
5
|
-
|
6
6
|
results = Coverage.result.select {|key, value| testing.include?(key)}
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
puts "#{total.select {|i| i > 0}.length}/#{total.length} executable lines covered"
|
11
|
-
puts
|
8
|
+
all = results.map(&:last).flatten.compact
|
9
|
+
print "\n#{all.reject(&:zero?).size}/#{all.size} executable lines covered\n\n"
|
12
10
|
|
13
11
|
results.each do |key, value|
|
14
12
|
next unless value.include?(0)
|
15
|
-
|
16
|
-
|
17
|
-
puts
|
18
|
-
File.readlines(key).zip(value).each_with_index do |(line, val), i|
|
19
|
-
print val == 0 ? "> " : " "
|
20
|
-
print "%3i %5s %s" % [(i + 1), val, line]
|
13
|
+
lines = File.readlines(key).zip(value).each_with_index.map do |(line,val),i|
|
14
|
+
"%-2s%3i %5s %s" % [(">" if val == 0), (i + 1), val, line]
|
21
15
|
end
|
22
|
-
|
23
|
-
puts
|
16
|
+
print "#{key}\n line calls code\n\n#{lines.join}\n\n"
|
24
17
|
end
|
25
18
|
end
|
26
19
|
|
27
20
|
Coverage.start
|
28
|
-
|
21
|
+
require_relative 'runner'
|
data/test/parser/request_test.rb
CHANGED
@@ -75,9 +75,17 @@ class ParserRequestTest < Test::Unit::TestCase
|
|
75
75
|
def test_invalid_path
|
76
76
|
parser = HTTPTools::Parser.new
|
77
77
|
|
78
|
-
assert_raise(HTTPTools::ParseError) do
|
78
|
+
error = assert_raise(HTTPTools::ParseError) do
|
79
79
|
parser << "GET \\ HTTP/1.1\r\n\r\n"
|
80
80
|
end
|
81
|
+
|
82
|
+
return unless "".respond_to?(:lines)
|
83
|
+
assert_equal(<<-MESSAGE.chomp, error.message)
|
84
|
+
URI or path not recognised at line 1, char 5
|
85
|
+
|
86
|
+
GET \\\\ HTTP/1.1\\r\\n
|
87
|
+
^
|
88
|
+
MESSAGE
|
81
89
|
end
|
82
90
|
|
83
91
|
def test_uri
|
@@ -119,17 +127,49 @@ class ParserRequestTest < Test::Unit::TestCase
|
|
119
127
|
def test_fragment_with_path
|
120
128
|
parser = HTTPTools::Parser.new
|
121
129
|
|
122
|
-
assert_raise(HTTPTools::ParseError) do
|
130
|
+
error = assert_raise(HTTPTools::ParseError) do
|
123
131
|
parser << "GET /foo#bar HTTP/1.1\r\n\r\n"
|
124
132
|
end
|
133
|
+
|
134
|
+
return unless "".respond_to?(:lines)
|
135
|
+
assert_equal(<<-MESSAGE.chomp, error.message)
|
136
|
+
URI or path not recognised at line 1, char 9
|
137
|
+
|
138
|
+
GET /foo#bar HTTP/1.1\\r\\n
|
139
|
+
^
|
140
|
+
MESSAGE
|
125
141
|
end
|
126
142
|
|
127
143
|
def test_fragment_with_uri
|
128
144
|
parser = HTTPTools::Parser.new
|
129
145
|
|
130
|
-
assert_raise(HTTPTools::ParseError) do
|
146
|
+
error = assert_raise(HTTPTools::ParseError) do
|
131
147
|
parser << "GET http://example.com/foo#bar HTTP/1.1\r\n\r\n"
|
132
148
|
end
|
149
|
+
|
150
|
+
return unless "".respond_to?(:lines)
|
151
|
+
assert_equal(<<-MESSAGE.chomp, error.message)
|
152
|
+
URI or path not recognised at line 1, char 27
|
153
|
+
|
154
|
+
GET http://example.com/foo#bar HTTP/1.1\\r\\n
|
155
|
+
^
|
156
|
+
MESSAGE
|
157
|
+
end
|
158
|
+
|
159
|
+
def test_fragment_with_unfinished_path
|
160
|
+
parser = HTTPTools::Parser.new
|
161
|
+
|
162
|
+
error = assert_raise(HTTPTools::ParseError) do
|
163
|
+
parser << "GET /foo#ba"
|
164
|
+
end
|
165
|
+
|
166
|
+
return unless "".respond_to?(:lines)
|
167
|
+
assert_equal(<<-MESSAGE.chomp, error.message)
|
168
|
+
URI or path not recognised at line 1, char 9
|
169
|
+
|
170
|
+
GET /foo#ba
|
171
|
+
^
|
172
|
+
MESSAGE
|
133
173
|
end
|
134
174
|
|
135
175
|
def test_with_header
|
@@ -286,7 +326,15 @@ class ParserRequestTest < Test::Unit::TestCase
|
|
286
326
|
def test_unknown_protocol
|
287
327
|
parser = HTTPTools::Parser.new
|
288
328
|
|
289
|
-
assert_raise(HTTPTools::ParseError) {parser << "GET / SPDY/1.1\r\n"}
|
329
|
+
error = assert_raise(HTTPTools::ParseError) {parser << "GET / SPDY/1.1\r\n"}
|
330
|
+
|
331
|
+
return unless "".respond_to?(:lines)
|
332
|
+
assert_equal(<<-MESSAGE.chomp, error.message)
|
333
|
+
Invalid version specifier at line 1, char 7
|
334
|
+
|
335
|
+
GET / SPDY/1.1\\r\\n
|
336
|
+
^
|
337
|
+
MESSAGE
|
290
338
|
end
|
291
339
|
|
292
340
|
def test_protocol_version
|
@@ -303,7 +351,15 @@ class ParserRequestTest < Test::Unit::TestCase
|
|
303
351
|
def test_protocol_without_version
|
304
352
|
parser = HTTPTools::Parser.new
|
305
353
|
|
306
|
-
assert_raise(HTTPTools::ParseError) {parser << "GET / HTTP\r\n\r\n"}
|
354
|
+
error = assert_raise(HTTPTools::ParseError) {parser << "GET / HTTP\r\n\r\n"}
|
355
|
+
|
356
|
+
return unless "".respond_to?(:lines)
|
357
|
+
assert_equal(<<-MESSAGE.chomp, error.message)
|
358
|
+
Invalid version specifier at line 1, char 11
|
359
|
+
|
360
|
+
GET / HTTP\\r\\n
|
361
|
+
^
|
362
|
+
MESSAGE
|
307
363
|
end
|
308
364
|
|
309
365
|
def test_one_dot_x_protocol_version
|
@@ -432,7 +488,7 @@ class ParserRequestTest < Test::Unit::TestCase
|
|
432
488
|
def test_not_a_http_request
|
433
489
|
parser = HTTPTools::Parser.new
|
434
490
|
|
435
|
-
assert_raise(HTTPTools::ParseError) {parser << "not a http request"}
|
491
|
+
assert_raise(HTTPTools::ParseError) {parser << "not a http/1.1 request"}
|
436
492
|
end
|
437
493
|
|
438
494
|
def test_data_past_end
|
@@ -458,6 +514,23 @@ class ParserRequestTest < Test::Unit::TestCase
|
|
458
514
|
assert_equal("get", result)
|
459
515
|
end
|
460
516
|
|
517
|
+
def test_invalid_method
|
518
|
+
parser = HTTPTools::Parser.new
|
519
|
+
|
520
|
+
error = assert_raise(HTTPTools::ParseError) do
|
521
|
+
parser << "G3T / HTTP/1.1\r\n\r\n"
|
522
|
+
end
|
523
|
+
|
524
|
+
return unless "".respond_to?(:lines)
|
525
|
+
assert_equal(<<-MESSAGE.chomp, error.message)
|
526
|
+
Protocol or method not recognised at line 1, char 2
|
527
|
+
|
528
|
+
G3T / HTTP/1.1\\r\\n
|
529
|
+
^
|
530
|
+
MESSAGE
|
531
|
+
end
|
532
|
+
|
533
|
+
|
461
534
|
def test_lowercase_http
|
462
535
|
parser = HTTPTools::Parser.new
|
463
536
|
version = nil
|
@@ -472,31 +545,68 @@ class ParserRequestTest < Test::Unit::TestCase
|
|
472
545
|
def test_invalid_version
|
473
546
|
parser = HTTPTools::Parser.new
|
474
547
|
|
475
|
-
assert_raise(HTTPTools::ParseError)
|
548
|
+
error = assert_raise(HTTPTools::ParseError) do
|
549
|
+
parser << "GET / HTTP/one dot one\r\n"
|
550
|
+
end
|
551
|
+
|
552
|
+
return unless "".respond_to?(:lines)
|
553
|
+
assert_equal(<<-MESSAGE.chomp, error.message)
|
554
|
+
Invalid version specifier at line 1, char 12
|
555
|
+
|
556
|
+
GET / HTTP/one dot one\\r\\n
|
557
|
+
^
|
558
|
+
MESSAGE
|
476
559
|
end
|
477
560
|
|
478
561
|
def test_invalid_header_key_with_control_character
|
479
562
|
parser = HTTPTools::Parser.new
|
480
563
|
|
481
|
-
assert_raise(HTTPTools::ParseError) do
|
564
|
+
error = assert_raise(HTTPTools::ParseError) do
|
482
565
|
parser << "GET / HTTP/1.1\r\nx-invalid\0key: text/plain\r\n"
|
483
566
|
end
|
567
|
+
|
568
|
+
return unless "".respond_to?(:lines)
|
569
|
+
null = "\000".dump.gsub(/"/, "")
|
570
|
+
assert_equal(<<-MESSAGE.chomp, error.message)
|
571
|
+
Illegal character in field name at line 2, char 10
|
572
|
+
|
573
|
+
x-invalid#{null}key: text/plain\\r\\n
|
574
|
+
^
|
575
|
+
MESSAGE
|
484
576
|
end
|
485
577
|
|
486
578
|
def test_invalid_header_key_with_non_ascii_character
|
487
579
|
parser = HTTPTools::Parser.new
|
488
580
|
|
489
|
-
assert_raise(HTTPTools::ParseError) do
|
490
|
-
parser << "GET / HTTP/1.1\r\nx-invalid\
|
581
|
+
error = assert_raise(HTTPTools::ParseError) do
|
582
|
+
parser << "GET / HTTP/1.1\r\nx-invalid\342\200\224key: text/plain\r\n"
|
491
583
|
end
|
584
|
+
|
585
|
+
em_dash = "\342\200\224".dump.gsub(/"/, "")
|
586
|
+
return unless "".respond_to?(:lines)
|
587
|
+
assert_equal(<<-MESSAGE.chomp, error.message)
|
588
|
+
Illegal character in field name at line 2, char 10
|
589
|
+
|
590
|
+
x-invalid#{em_dash}key: text/plain\\r\\n
|
591
|
+
#{" " * (em_dash.length / 4)}^
|
592
|
+
MESSAGE
|
492
593
|
end
|
493
594
|
|
494
|
-
def
|
595
|
+
def test_invalid_header_value_with_non_control_character
|
495
596
|
parser = HTTPTools::Parser.new
|
496
597
|
|
497
|
-
assert_raise(HTTPTools::ParseError) do
|
498
|
-
parser << "GET / HTTP/1.1\r\nAccept: \
|
598
|
+
error = assert_raise(HTTPTools::ParseError) do
|
599
|
+
parser << "GET / HTTP/1.1\r\nAccept: text\000plain\r\n"
|
499
600
|
end
|
601
|
+
|
602
|
+
return unless "".respond_to?(:lines)
|
603
|
+
null = "\000".dump.gsub(/"/, "")
|
604
|
+
assert_equal(<<-MESSAGE.chomp, error.message)
|
605
|
+
Illegal character in field body at line 2, char 13
|
606
|
+
|
607
|
+
Accept: text#{null}plain\\r\\n
|
608
|
+
^
|
609
|
+
MESSAGE
|
500
610
|
end
|
501
611
|
|
502
612
|
def test_error_callback
|
@@ -721,4 +831,12 @@ class ParserRequestTest < Test::Unit::TestCase
|
|
721
831
|
assert_match(/#<HTTPTools::Parser:0x[a-f0-9]+ line 1, char 1 start>/, parser.inspect)
|
722
832
|
end
|
723
833
|
|
724
|
-
|
834
|
+
def test_inspect_position
|
835
|
+
parser = HTTPTools::Parser.new
|
836
|
+
|
837
|
+
parser << "GET / HTTP/1.1\r\nHost: foo."
|
838
|
+
|
839
|
+
assert_match(/#<HTTPTools::Parser:0x[a-f0-9]+ line 2, char 7 value>/, parser.inspect)
|
840
|
+
end
|
841
|
+
|
842
|
+
end
|
@@ -290,6 +290,26 @@ class ParserResponseTest < Test::Unit::TestCase
|
|
290
290
|
assert_equal({"Content-Type" => "text/html; charset=utf-8"}, headers)
|
291
291
|
end
|
292
292
|
|
293
|
+
def test_multi_line_header_invalid_value
|
294
|
+
parser = HTTPTools::Parser.new
|
295
|
+
|
296
|
+
parser << "HTTP/1.1 200 OK\r\n"
|
297
|
+
parser << "Content-Type: text/html;\r\n"
|
298
|
+
|
299
|
+
error = assert_raise(HTTPTools::ParseError) do
|
300
|
+
parser << " charset=\0\r\n\r\n"
|
301
|
+
end
|
302
|
+
|
303
|
+
return unless "".respond_to?(:lines)
|
304
|
+
null = "\000".dump.gsub(/"/, "")
|
305
|
+
assert_equal(<<-MESSAGE.chomp, error.message)
|
306
|
+
Illegal character in field body at line 3, char 10
|
307
|
+
|
308
|
+
charset=#{null}\\r\\n
|
309
|
+
^
|
310
|
+
MESSAGE
|
311
|
+
end
|
312
|
+
|
293
313
|
def test_header_value_leading_and_trailing_whitespace_is_stripped
|
294
314
|
parser = HTTPTools::Parser.new
|
295
315
|
headers = nil
|
@@ -380,6 +400,23 @@ class ParserResponseTest < Test::Unit::TestCase
|
|
380
400
|
assert(parser.finished?, "parser should be finished")
|
381
401
|
end
|
382
402
|
|
403
|
+
def test_invalid_header_key_with_control_character
|
404
|
+
parser = HTTPTools::Parser.new
|
405
|
+
|
406
|
+
error = assert_raise(HTTPTools::ParseError) do
|
407
|
+
parser << "HTTP/1.0 200 OK\r\nx-invalid\0key: valid key\r\n"
|
408
|
+
end
|
409
|
+
|
410
|
+
return unless "".respond_to?(:lines)
|
411
|
+
null = "\000".dump.gsub(/"/, "")
|
412
|
+
assert_equal(<<-MESSAGE.chomp, error.message)
|
413
|
+
Illegal character in field name at line 2, char 10
|
414
|
+
|
415
|
+
x-invalid#{null}key: valid key\\r\\n
|
416
|
+
^
|
417
|
+
MESSAGE
|
418
|
+
end
|
419
|
+
|
383
420
|
def test_apple_dot_com
|
384
421
|
parser = HTTPTools::Parser.new
|
385
422
|
code, message, headers = nil
|
@@ -646,6 +683,29 @@ class ParserResponseTest < Test::Unit::TestCase
|
|
646
683
|
assert(parser.finished?, "parser should be finished")
|
647
684
|
end
|
648
685
|
|
686
|
+
def test_body_with_no_headers
|
687
|
+
parser = HTTPTools::Parser.new
|
688
|
+
code, message, headers = nil
|
689
|
+
body = ""
|
690
|
+
|
691
|
+
parser.add_listener(:header) do
|
692
|
+
code = parser.status_code
|
693
|
+
message = parser.message
|
694
|
+
headers = parser.header
|
695
|
+
end
|
696
|
+
parser.add_listener(:stream) {|chunk| body << chunk}
|
697
|
+
|
698
|
+
parser << "HTTP/1.1 200 OK\r\n\r\n"
|
699
|
+
parser << "<h1>hello world</h1>"
|
700
|
+
parser.finish # notify parser the connection has closed
|
701
|
+
|
702
|
+
assert_equal(200, code)
|
703
|
+
assert_equal("OK", message)
|
704
|
+
assert_equal({}, headers)
|
705
|
+
assert_equal("<h1>hello world</h1>", body)
|
706
|
+
assert(parser.finished?, "parser should be finished")
|
707
|
+
end
|
708
|
+
|
649
709
|
def test_chunked
|
650
710
|
parser = HTTPTools::Parser.new
|
651
711
|
code, message, headers = nil
|
@@ -865,9 +925,17 @@ class ParserResponseTest < Test::Unit::TestCase
|
|
865
925
|
def test_html_body_only_not_allowed
|
866
926
|
parser = HTTPTools::Parser.new
|
867
927
|
|
868
|
-
assert_raise(HTTPTools::ParseError) do
|
928
|
+
error = assert_raise(HTTPTools::ParseError) do
|
869
929
|
parser << "<html><p>HTTP is hard</p></html>"
|
870
930
|
end
|
931
|
+
|
932
|
+
return unless "".respond_to?(:lines)
|
933
|
+
assert_equal(<<-MESSAGE.chomp, error.message)
|
934
|
+
Protocol or method not recognised at line 1, char 1
|
935
|
+
|
936
|
+
<html><p>HTTP is hard</p></html>
|
937
|
+
^
|
938
|
+
MESSAGE
|
871
939
|
end
|
872
940
|
|
873
941
|
def test_html_body_only_allowed
|
@@ -1155,6 +1223,29 @@ class ParserResponseTest < Test::Unit::TestCase
|
|
1155
1223
|
assert_equal({"X-Test" => "one two"}, trailer)
|
1156
1224
|
end
|
1157
1225
|
|
1226
|
+
def test_multi_line_trailer_invalid_value
|
1227
|
+
parser = HTTPTools::Parser.new
|
1228
|
+
|
1229
|
+
parser << "HTTP/1.1 200 OK\r\n"
|
1230
|
+
parser << "Transfer-Encoding: chunked\r\n"
|
1231
|
+
parser << "Trailer: X-Test\r\n\r\n"
|
1232
|
+
parser << "14\r\n<h1>Hello world</h1>\r\n0\r\n"
|
1233
|
+
parser << "X-Test: one\r\n"
|
1234
|
+
|
1235
|
+
error = assert_raise(HTTPTools::ParseError) do
|
1236
|
+
parser << " \0two\r\n"
|
1237
|
+
end
|
1238
|
+
|
1239
|
+
return unless "".respond_to?(:lines)
|
1240
|
+
null = "\000".dump.gsub(/"/, "")
|
1241
|
+
assert_equal(<<-MESSAGE.chomp, error.message)
|
1242
|
+
Illegal character in field body at line 9, char 2
|
1243
|
+
|
1244
|
+
#{null}two\\r\\n
|
1245
|
+
^
|
1246
|
+
MESSAGE
|
1247
|
+
end
|
1248
|
+
|
1158
1249
|
def test_trailer_value_leading_and_trailing_whitespace_is_stripped
|
1159
1250
|
parser = HTTPTools::Parser.new
|
1160
1251
|
trailer = nil
|
@@ -1237,9 +1328,18 @@ class ParserResponseTest < Test::Unit::TestCase
|
|
1237
1328
|
parser << "Transfer-Encoding: chunked\r\nTrailer: X-Checksum\r\n\r\n"
|
1238
1329
|
parser << "14\r\n<h1>Hello world</h1>\r\n0\r\n"
|
1239
1330
|
|
1240
|
-
assert_raise(HTTPTools::ParseError) do
|
1331
|
+
error = assert_raise(HTTPTools::ParseError) do
|
1241
1332
|
parser << "x-invalid\0key: value\r\n\r\n"
|
1242
1333
|
end
|
1334
|
+
|
1335
|
+
return unless "".respond_to?(:lines)
|
1336
|
+
null = "\000".dump.gsub(/"/, "")
|
1337
|
+
assert_equal(<<-MESSAGE.chomp, error.message)
|
1338
|
+
Illegal character in field name at line 8, char 10
|
1339
|
+
|
1340
|
+
x-invalid#{null}key: value\\r\\n
|
1341
|
+
^
|
1342
|
+
MESSAGE
|
1243
1343
|
end
|
1244
1344
|
|
1245
1345
|
def test_invalid_trailer_value
|
@@ -1252,21 +1352,65 @@ class ParserResponseTest < Test::Unit::TestCase
|
|
1252
1352
|
parser << "Transfer-Encoding: chunked\r\nTrailer: X-Checksum\r\n\r\n"
|
1253
1353
|
parser << "14\r\n<h1>Hello world</h1>\r\n0\r\n"
|
1254
1354
|
|
1255
|
-
assert_raise(HTTPTools::ParseError) do
|
1355
|
+
error = assert_raise(HTTPTools::ParseError) do
|
1256
1356
|
parser << "x-test: inva\0lid\r\n\r\n"
|
1257
1357
|
end
|
1358
|
+
|
1359
|
+
return unless "".respond_to?(:lines)
|
1360
|
+
null = "\000".dump.gsub(/"/, "")
|
1361
|
+
assert_equal(<<-MESSAGE.chomp, error.message)
|
1362
|
+
Illegal character in field body at line 8, char 13
|
1363
|
+
|
1364
|
+
x-test: inva#{null}lid\\r\\n
|
1365
|
+
^
|
1366
|
+
MESSAGE
|
1258
1367
|
end
|
1259
1368
|
|
1369
|
+
def test_invalid_protocol
|
1370
|
+
parser = HTTPTools::Parser.new
|
1371
|
+
|
1372
|
+
error = assert_raise(HTTPTools::ParseError) do
|
1373
|
+
parser << "HTTZ/1.1 200 OX\r\n"
|
1374
|
+
end
|
1375
|
+
|
1376
|
+
return unless "".respond_to?(:lines)
|
1377
|
+
assert_equal(<<-MESSAGE.chomp, error.message)
|
1378
|
+
Protocol or method not recognised at line 1, char 4
|
1379
|
+
|
1380
|
+
HTTZ/1.1 200 OX\\r\\n
|
1381
|
+
^
|
1382
|
+
MESSAGE
|
1383
|
+
end
|
1384
|
+
|
1385
|
+
|
1260
1386
|
def test_invalid_version
|
1261
1387
|
parser = HTTPTools::Parser.new
|
1262
1388
|
|
1263
|
-
assert_raise(HTTPTools::ParseError)
|
1389
|
+
error = assert_raise(HTTPTools::ParseError) do
|
1390
|
+
parser << "HTTP/one dot one 200 OK"
|
1391
|
+
end
|
1392
|
+
|
1393
|
+
return unless "".respond_to?(:lines)
|
1394
|
+
assert_equal(<<-MESSAGE.chomp, error.message)
|
1395
|
+
Invalid version specifier at line 1, char 6
|
1396
|
+
|
1397
|
+
HTTP/one dot one 200 OK
|
1398
|
+
^
|
1399
|
+
MESSAGE
|
1264
1400
|
end
|
1265
1401
|
|
1266
1402
|
def test_invalid_status
|
1267
1403
|
parser = HTTPTools::Parser.new
|
1268
1404
|
|
1269
|
-
assert_raise(HTTPTools::ParseError) {parser << "HTTP/1.1 0 Fail"}
|
1405
|
+
error = assert_raise(HTTPTools::ParseError) {parser << "HTTP/1.1 0 Fail"}
|
1406
|
+
|
1407
|
+
return unless "".respond_to?(:lines)
|
1408
|
+
assert_equal(<<-MESSAGE.chomp, error.message)
|
1409
|
+
Invalid status line at line 1, char 11
|
1410
|
+
|
1411
|
+
HTTP/1.1 0 Fail
|
1412
|
+
^
|
1413
|
+
MESSAGE
|
1270
1414
|
end
|
1271
1415
|
|
1272
1416
|
def test_finish_early
|
metadata
CHANGED
@@ -1,24 +1,33 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: http_tools
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 7
|
5
5
|
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 4
|
9
|
+
- 4
|
10
|
+
version: 0.4.4
|
6
11
|
platform: ruby
|
7
|
-
authors:
|
12
|
+
authors:
|
8
13
|
- Matthew Sadler
|
9
14
|
autorequire:
|
10
15
|
bindir: bin
|
11
16
|
cert_chain: []
|
12
|
-
|
17
|
+
|
18
|
+
date: 2011-12-20 00:00:00 +00:00
|
19
|
+
default_executable:
|
13
20
|
dependencies: []
|
14
|
-
|
15
|
-
|
21
|
+
|
22
|
+
description: A fast-as-possible pure Ruby HTTP parser plus associated lower level utilities to aid working with HTTP and the web.
|
16
23
|
email: mat@sourcetagsandcodes.com
|
17
24
|
executables: []
|
25
|
+
|
18
26
|
extensions: []
|
19
|
-
|
27
|
+
|
28
|
+
extra_rdoc_files:
|
20
29
|
- README.rdoc
|
21
|
-
files:
|
30
|
+
files:
|
22
31
|
- lib/http_tools/builder.rb
|
23
32
|
- lib/http_tools/encoding.rb
|
24
33
|
- lib/http_tools/parser.rb
|
@@ -44,32 +53,42 @@ files:
|
|
44
53
|
- example/websocket_client.rb
|
45
54
|
- example/websocket_server.rb
|
46
55
|
- README.rdoc
|
56
|
+
has_rdoc: true
|
47
57
|
homepage: http://github.com/matsadler/http_tools
|
48
58
|
licenses: []
|
59
|
+
|
49
60
|
post_install_message:
|
50
|
-
rdoc_options:
|
61
|
+
rdoc_options:
|
51
62
|
- --main
|
52
63
|
- README.rdoc
|
53
64
|
- --charset
|
54
65
|
- utf-8
|
55
|
-
require_paths:
|
66
|
+
require_paths:
|
56
67
|
- lib
|
57
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
68
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
58
69
|
none: false
|
59
|
-
requirements:
|
60
|
-
- -
|
61
|
-
- !ruby/object:Gem::Version
|
62
|
-
|
63
|
-
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
hash: 3
|
74
|
+
segments:
|
75
|
+
- 0
|
76
|
+
version: "0"
|
77
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
64
78
|
none: false
|
65
|
-
requirements:
|
66
|
-
- -
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
hash: 3
|
83
|
+
segments:
|
84
|
+
- 0
|
85
|
+
version: "0"
|
69
86
|
requirements: []
|
87
|
+
|
70
88
|
rubyforge_project:
|
71
|
-
rubygems_version: 1.
|
89
|
+
rubygems_version: 1.6.2
|
72
90
|
signing_key:
|
73
91
|
specification_version: 3
|
74
92
|
summary: Pure Ruby HTTP parser and friends
|
75
93
|
test_files: []
|
94
|
+
|