http_tools 0.4.0 → 0.4.1
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.
- data/README.rdoc +6 -2
- data/example/websocket_client.rb +96 -0
- data/example/websocket_server.rb +92 -0
- data/lib/http_tools/parser.rb +38 -3
- data/test/encoding/url_encoding_test.rb +2 -1
- data/test/encoding/www_form_test.rb +16 -5
- data/test/parser/request_test.rb +116 -0
- data/test/parser/response_test.rb +86 -1
- data/test/runner.rb +10 -1
- metadata +6 -4
data/README.rdoc
CHANGED
@@ -12,6 +12,10 @@ Written purely in Ruby, with no dependencies outside of the standard library, it
|
|
12
12
|
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
|
+
All tests are currently run (and pass) against ruby 1.8.6, 1.8.7, 1.9.2, jruby 1.6.2, rubinius 1.2.4, MacRuby 0.10
|
16
|
+
|
17
|
+
Performance tuning is mainly aimed at ruby 1.9, with ruby 1.8 and jruby taken in to consideration. jruby is generally fastest.
|
18
|
+
|
15
19
|
== HTTPTools::Parser
|
16
20
|
|
17
21
|
HTTPTools::Parser is a HTTP request & response parser with an evented API.
|
@@ -21,8 +25,8 @@ possible.
|
|
21
25
|
=== Example
|
22
26
|
|
23
27
|
parser = HTTPTools::Parser.new
|
24
|
-
parser.on(:header) do
|
25
|
-
puts parser.status_code + " " + parser.
|
28
|
+
parser.on(:header) do
|
29
|
+
puts parser.status_code + " " + parser.message
|
26
30
|
puts parser.header.inspect
|
27
31
|
end
|
28
32
|
parser.on(:finish) {print parser.body}
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'digest/md5'
|
3
|
+
require 'uri'
|
4
|
+
require 'rubygems'
|
5
|
+
require 'http_tools'
|
6
|
+
|
7
|
+
# Very basic implmentation of a WebSocket client according to
|
8
|
+
# http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76
|
9
|
+
#
|
10
|
+
# Example:
|
11
|
+
# sock = WebSocket.new("ws://echo.websocket.org/")
|
12
|
+
# sock.puts("test")
|
13
|
+
# puts sock.gets
|
14
|
+
#
|
15
|
+
class WebSocket
|
16
|
+
attr_accessor :host, :port, :path, :origin
|
17
|
+
|
18
|
+
def initialize(url, origin="localhost")
|
19
|
+
uri = URI.parse(url)
|
20
|
+
@host = uri.host
|
21
|
+
@port = uri.port || 80
|
22
|
+
@path = uri.path
|
23
|
+
@origin = origin
|
24
|
+
@socket = TCPSocket.new(host, port)
|
25
|
+
handshake(@socket)
|
26
|
+
end
|
27
|
+
|
28
|
+
def gets
|
29
|
+
get_frame(@socket)
|
30
|
+
end
|
31
|
+
|
32
|
+
def puts(str)
|
33
|
+
put_frame(@socket, str)
|
34
|
+
nil
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
def handshake(socket)
|
39
|
+
number1, key1 = number_and_key
|
40
|
+
number2, key2 = number_and_key
|
41
|
+
key3 = (0..7).to_a.inject("") {|memo, i| memo << rand(256)}
|
42
|
+
header = {
|
43
|
+
"Connection" => "Upgrade",
|
44
|
+
"Upgrade" => "WebSocket",
|
45
|
+
"Origin" => origin,
|
46
|
+
"Sec-WebSocket-Key1" => key1,
|
47
|
+
"Sec-WebSocket-Key2" => key2}
|
48
|
+
hostport = host
|
49
|
+
hostport += port.to_s unless port == 80
|
50
|
+
socket << HTTPTools::Builder.request(:get, hostport, path, header) << key3
|
51
|
+
|
52
|
+
parser = HTTPTools::Parser.new
|
53
|
+
code = nil
|
54
|
+
|
55
|
+
parser.on(:header) do
|
56
|
+
raise "status is not 101" unless parser.status_code == 101
|
57
|
+
unless parser.header["Sec-WebSocket-Origin"] == origin
|
58
|
+
raise "origin missmatch"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
parser << socket.sysread(1024 * 16) until parser.finished?
|
62
|
+
reply = parser.rest
|
63
|
+
reply << socket.read(16 - reply.length) if reply.length < 16
|
64
|
+
unless reply == Digest::MD5.digest([number1, number2].pack("N*") + key3)
|
65
|
+
raise "handshake failed"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def number_and_key
|
70
|
+
spaces = rand(12) + 1
|
71
|
+
number = rand((4_294_967_295 / spaces) + 1)
|
72
|
+
key = (number * spaces).to_s
|
73
|
+
chars = ("!".."/").to_a + (":".."~").to_a
|
74
|
+
(rand(12) + 1).times {key[rand(key.length), 0] = chars[rand(chars.length)]}
|
75
|
+
spaces.times {key[rand(key.length - 2) + 1, 0] = " "}
|
76
|
+
[number, key]
|
77
|
+
end
|
78
|
+
|
79
|
+
def get_frame(socket)
|
80
|
+
type = socket.getbyte
|
81
|
+
raise "don't understand frame type #{type}" unless type & 128 == 0
|
82
|
+
str = socket.gets("\377")
|
83
|
+
str.chomp!("\377")
|
84
|
+
str.force_encoding("UTF-8") if str.respond_to?(:force_encoding)
|
85
|
+
str
|
86
|
+
end
|
87
|
+
|
88
|
+
def put_frame(socket, str)
|
89
|
+
str.force_encoding("UTF-8") if str.respond_to?(:force_encoding)
|
90
|
+
socket.putc("\000")
|
91
|
+
written = socket.write(str)
|
92
|
+
socket.putc("\377")
|
93
|
+
written
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'digest/md5'
|
3
|
+
require 'rubygems'
|
4
|
+
require 'http_tools'
|
5
|
+
|
6
|
+
module WebSocket
|
7
|
+
|
8
|
+
# Very basic implmentation of a WebSocket server according to
|
9
|
+
# http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76
|
10
|
+
#
|
11
|
+
# Example:
|
12
|
+
# server = WebSocket::Server.new("localhost", 9292)
|
13
|
+
# while sock = server.accept
|
14
|
+
# Thread.new {loop {sock.puts sock.gets}}
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# Test at http://websocket.org/echo.html, with location ws://localhost:9292/
|
18
|
+
# Works with Safari 5.1 and Chrome 12
|
19
|
+
#
|
20
|
+
class Server
|
21
|
+
def initialize(host, port)
|
22
|
+
@server = TCPServer.new(host, port)
|
23
|
+
end
|
24
|
+
|
25
|
+
def accept
|
26
|
+
ServerSocket.new(@server.accept)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class ServerSocket
|
31
|
+
def initialize(socket)
|
32
|
+
@socket = socket
|
33
|
+
handshake(socket)
|
34
|
+
end
|
35
|
+
|
36
|
+
def gets
|
37
|
+
get_frame(@socket)
|
38
|
+
end
|
39
|
+
|
40
|
+
def puts(str)
|
41
|
+
put_frame(@socket, str)
|
42
|
+
nil
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
def handshake(socket)
|
47
|
+
parser = HTTPTools::Parser.new
|
48
|
+
key1, key2 = nil
|
49
|
+
response_header = {"Connection" => "Upgrade", "Upgrade" => "WebSocket"}
|
50
|
+
|
51
|
+
parser.on(:header) do
|
52
|
+
key1 = parser.header["Sec-WebSocket-Key1"]
|
53
|
+
key2 = parser.header["Sec-WebSocket-Key2"]
|
54
|
+
location = "ws://" + parser.header["Host"] + parser.path_info
|
55
|
+
response_header["Sec-WebSocket-Location"] = location
|
56
|
+
response_header["Sec-WebSocket-Origin"] = parser.header["Origin"]
|
57
|
+
end
|
58
|
+
parser << socket.sysread(1024 * 16) until parser.finished?
|
59
|
+
key3 = parser.rest
|
60
|
+
key3 << socket.read(8 - key3.length) if key3.length < 8
|
61
|
+
|
62
|
+
socket << HTTPTools::Builder.response(101, response_header)
|
63
|
+
socket << response_key(key1, key2, key3)
|
64
|
+
end
|
65
|
+
|
66
|
+
def response_key(key1, key2, key3)
|
67
|
+
Digest::MD5.digest([key1, key2].map(&method(:process_key)).join + key3)
|
68
|
+
end
|
69
|
+
|
70
|
+
def process_key(key)
|
71
|
+
[key.scan(/\d+/).join.to_i / key.scan(/ /).length].pack("N*")
|
72
|
+
end
|
73
|
+
|
74
|
+
def get_frame(socket)
|
75
|
+
type = socket.getbyte
|
76
|
+
raise unless type == 0
|
77
|
+
str = socket.gets("\377")
|
78
|
+
str.chomp!("\377")
|
79
|
+
str.force_encoding("UTF-8") if str.respond_to?(:force_encoding)
|
80
|
+
str
|
81
|
+
end
|
82
|
+
|
83
|
+
def put_frame(socket, str)
|
84
|
+
str.force_encoding("UTF-8") if str.respond_to?(:force_encoding)
|
85
|
+
socket.putc("\000")
|
86
|
+
written = socket.write(str)
|
87
|
+
socket.putc("\377")
|
88
|
+
written
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
end
|
data/lib/http_tools/parser.rb
CHANGED
@@ -12,8 +12,8 @@ module HTTPTools
|
|
12
12
|
#
|
13
13
|
# Example:
|
14
14
|
# parser = HTTPTools::Parser.new
|
15
|
-
# parser.on(:header) do
|
16
|
-
# puts parser.status_code + " " + parser.
|
15
|
+
# parser.on(:header) do
|
16
|
+
# puts parser.status_code + " " + parser.message
|
17
17
|
# puts parser.header.inspect
|
18
18
|
# end
|
19
19
|
# parser.on(:finish) {print parser.body}
|
@@ -75,6 +75,9 @@ module HTTPTools
|
|
75
75
|
# Allow responses with no status line or headers if it looks like HTML.
|
76
76
|
attr_accessor :allow_html_without_header
|
77
77
|
|
78
|
+
# Max size for a 'Transfer-Encoding: chunked' body chunk. nil for no limit.
|
79
|
+
attr_accessor :max_chunk_size
|
80
|
+
|
78
81
|
# :call-seq: Parser.new -> parser
|
79
82
|
#
|
80
83
|
# Create a new HTTPTools::Parser.
|
@@ -87,7 +90,9 @@ module HTTPTools
|
|
87
90
|
@force_no_body = nil
|
88
91
|
@allow_html_without_header = nil
|
89
92
|
@force_trailer = nil
|
93
|
+
@max_chunk_size = nil
|
90
94
|
@status_code = nil
|
95
|
+
@header_done = nil
|
91
96
|
@content_left = nil
|
92
97
|
@chunked = nil
|
93
98
|
@body = nil
|
@@ -196,6 +201,15 @@ module HTTPTools
|
|
196
201
|
@state == :end_of_message
|
197
202
|
end
|
198
203
|
|
204
|
+
# :call-seq: parser.header? -> bool
|
205
|
+
#
|
206
|
+
# Returns true when the parser has received the complete header, false
|
207
|
+
# otherwise.
|
208
|
+
#
|
209
|
+
def header?
|
210
|
+
@header_done
|
211
|
+
end
|
212
|
+
|
199
213
|
# :call-seq: parser.rest -> string
|
200
214
|
#
|
201
215
|
# Returns unconsumed data in the parser's buffer.
|
@@ -204,6 +218,14 @@ module HTTPTools
|
|
204
218
|
@buffer.rest
|
205
219
|
end
|
206
220
|
|
221
|
+
# :call-seq: parser.rest_size -> int
|
222
|
+
#
|
223
|
+
# Returns the size in bytes of the unconsumed data in the parser's buffer.
|
224
|
+
#
|
225
|
+
def rest_size
|
226
|
+
@buffer.rest_size
|
227
|
+
end
|
228
|
+
|
207
229
|
# :call-seq: parser.reset -> parser
|
208
230
|
#
|
209
231
|
# Reset the parser so it can be used to process a new request.
|
@@ -219,12 +241,17 @@ module HTTPTools
|
|
219
241
|
@request_uri = nil
|
220
242
|
@version = nil
|
221
243
|
@status_code = nil
|
244
|
+
@header_done = nil
|
222
245
|
@header = {}
|
223
246
|
@trailer = {}
|
224
247
|
@last_key = nil
|
225
248
|
@content_left = nil
|
226
249
|
@chunked = nil
|
227
250
|
@trailer_expected = nil
|
251
|
+
@body = nil
|
252
|
+
if @stream_callback == method(:stream_callback)
|
253
|
+
@stream_callback = method(:setup_stream_callback)
|
254
|
+
end
|
228
255
|
self
|
229
256
|
end
|
230
257
|
|
@@ -406,11 +433,17 @@ module HTTPTools
|
|
406
433
|
end
|
407
434
|
|
408
435
|
def start_body
|
436
|
+
@header_done = true
|
409
437
|
if @request_method && !(@content_left || @chunked) ||
|
410
438
|
NO_BODY.key?(@status_code) || @force_no_body
|
411
439
|
end_of_message
|
412
440
|
elsif @content_left
|
413
441
|
@buffer = [@buffer.rest]
|
442
|
+
if @content_left >= @scanner.rest_size
|
443
|
+
@scanner.terminate
|
444
|
+
else
|
445
|
+
@scanner.pos += @content_left
|
446
|
+
end
|
414
447
|
body_with_length
|
415
448
|
elsif @chunked
|
416
449
|
@trailer_expected = @header.any? {|k,v| TRAILER.casecmp(k) == 0}
|
@@ -426,7 +459,7 @@ module HTTPTools
|
|
426
459
|
if !chunk.empty?
|
427
460
|
chunk_length = chunk.length
|
428
461
|
if chunk_length > @content_left
|
429
|
-
@scanner << chunk.slice!(@content_left..-1)
|
462
|
+
@scanner.terminate << chunk.slice!(@content_left..-1)
|
430
463
|
end
|
431
464
|
@stream_callback.call(chunk) if @stream_callback
|
432
465
|
@content_left -= chunk_length
|
@@ -458,6 +491,8 @@ module HTTPTools
|
|
458
491
|
else
|
459
492
|
break end_of_message
|
460
493
|
end
|
494
|
+
elsif @max_chunk_size && chunk_length > @max_chunk_size
|
495
|
+
raise ParseError.new("Maximum chunk size exceeded")
|
461
496
|
end
|
462
497
|
|
463
498
|
begin
|
@@ -4,18 +4,29 @@ require 'test/unit'
|
|
4
4
|
|
5
5
|
class WWWFormTest < Test::Unit::TestCase
|
6
6
|
|
7
|
+
def ruby_one_nine_or_greater?
|
8
|
+
ruby_version = RUBY_VERSION.split(".").map {|d| d.to_i}
|
9
|
+
ruby_version[0] > 1 || (ruby_version[0] == 1 && ruby_version[1] >= 9)
|
10
|
+
end
|
11
|
+
|
7
12
|
def test_encode
|
8
13
|
result = HTTPTools::Encoding.www_form_encode("foo" => "bar", "baz" => "qux")
|
9
14
|
|
10
|
-
|
11
|
-
|
15
|
+
if ruby_one_nine_or_greater?
|
16
|
+
assert_equal("foo=bar&baz=qux", result)
|
17
|
+
else
|
18
|
+
assert_equal(["baz=qux", "foo=bar"], result.split("&").sort)
|
19
|
+
end
|
12
20
|
end
|
13
21
|
|
14
22
|
def test_encode_with_array
|
15
23
|
result = HTTPTools::Encoding.www_form_encode("lang" => ["en", "fr"], "q" => ["foo", "bar"])
|
16
24
|
|
17
|
-
|
18
|
-
|
25
|
+
if ruby_one_nine_or_greater?
|
26
|
+
assert_equal("lang=en&lang=fr&q=foo&q=bar", result)
|
27
|
+
else
|
28
|
+
assert_equal(["lang=en", "lang=fr", "q=bar", "q=foo"], result.split("&").sort)
|
29
|
+
end
|
19
30
|
end
|
20
31
|
|
21
32
|
def test_decode
|
@@ -39,4 +50,4 @@ class WWWFormTest < Test::Unit::TestCase
|
|
39
50
|
assert_equal(orginal, decoded)
|
40
51
|
end
|
41
52
|
|
42
|
-
end
|
53
|
+
end
|
data/test/parser/request_test.rb
CHANGED
@@ -379,6 +379,56 @@ class ParserRequestTest < Test::Unit::TestCase
|
|
379
379
|
assert_equal(2, calls)
|
380
380
|
end
|
381
381
|
|
382
|
+
def test_default_body_with_reset
|
383
|
+
parser = HTTPTools::Parser.new
|
384
|
+
|
385
|
+
parser << "POST /example HTTP/1.1\r\n"
|
386
|
+
parser << "Host: www.example.com\r\n"
|
387
|
+
parser << "Content-Length: 3\r\n\r\n"
|
388
|
+
parser << "foo"
|
389
|
+
|
390
|
+
assert_equal("foo", parser.body)
|
391
|
+
|
392
|
+
parser.reset
|
393
|
+
|
394
|
+
parser << "POST /example HTTP/1.1\r\n"
|
395
|
+
parser << "Host: www.example.com\r\n"
|
396
|
+
parser << "Content-Length: 3\r\n\r\n"
|
397
|
+
parser << "bar"
|
398
|
+
|
399
|
+
assert_equal("bar", parser.body)
|
400
|
+
end
|
401
|
+
|
402
|
+
def test_rest_with_request_in_one_chunk
|
403
|
+
parser = HTTPTools::Parser.new
|
404
|
+
|
405
|
+
parser << "POST /example HTTP/1.1\r\nContent-Length: 4\r\n\r\ntest"
|
406
|
+
|
407
|
+
assert_equal("test", parser.body)
|
408
|
+
assert_equal("", parser.rest)
|
409
|
+
end
|
410
|
+
|
411
|
+
def test_rest_with_request_and_next_in_one_chunk
|
412
|
+
parser = HTTPTools::Parser.new
|
413
|
+
|
414
|
+
parser << "POST /example HTTP/1.1\r\nContent-Length: 4\r\n\r\ntestPOST /ex"
|
415
|
+
|
416
|
+
assert_equal("test", parser.body)
|
417
|
+
assert_equal("POST /ex", parser.rest)
|
418
|
+
end
|
419
|
+
|
420
|
+
def test_rest_size
|
421
|
+
parser = HTTPTools::Parser.new
|
422
|
+
|
423
|
+
parser << "GET /foo"
|
424
|
+
|
425
|
+
assert_equal("/foo".length, parser.rest_size)
|
426
|
+
|
427
|
+
parser << " HTTP/1.1\r\nHost: www.example.com"
|
428
|
+
|
429
|
+
assert_equal("www.example.com".length, parser.rest_size)
|
430
|
+
end
|
431
|
+
|
382
432
|
def test_not_a_http_request
|
383
433
|
parser = HTTPTools::Parser.new
|
384
434
|
|
@@ -458,6 +508,72 @@ class ParserRequestTest < Test::Unit::TestCase
|
|
458
508
|
assert_instance_of(HTTPTools::ParseError, error)
|
459
509
|
end
|
460
510
|
|
511
|
+
def test_upgrade_websocket_hixie_76
|
512
|
+
parser = HTTPTools::Parser.new
|
513
|
+
method, path, headers = nil
|
514
|
+
|
515
|
+
parser.add_listener(:header) do
|
516
|
+
method = parser.request_method
|
517
|
+
path = parser.path_info
|
518
|
+
headers = parser.header
|
519
|
+
end
|
520
|
+
|
521
|
+
parser << "GET /demo HTTP/1.1\r\n"
|
522
|
+
parser << "Host: example.com\r\n"
|
523
|
+
parser << "Connection: Upgrade\r\n"
|
524
|
+
parser << "Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n"
|
525
|
+
parser << "Sec-WebSocket-Protocol: sample\r\n"
|
526
|
+
parser << "Upgrade: WebSocket\r\n"
|
527
|
+
parser << "Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n"
|
528
|
+
parser << "Origin: http://example.com\r\n\r\n^n:ds[4U"
|
529
|
+
|
530
|
+
assert_equal("GET", method)
|
531
|
+
assert_equal("/demo", path)
|
532
|
+
assert_equal({
|
533
|
+
"Host" => "example.com",
|
534
|
+
"Connection" => "Upgrade",
|
535
|
+
"Sec-WebSocket-Key2" => "12998 5 Y3 1 .P00",
|
536
|
+
"Upgrade" => "WebSocket",
|
537
|
+
"Sec-WebSocket-Protocol" => "sample",
|
538
|
+
"Sec-WebSocket-Key1" => "4 @1 46546xW%0l 1 5",
|
539
|
+
"Origin" => "http://example.com"}, headers)
|
540
|
+
assert(parser.finished?, "Parser should be finished.")
|
541
|
+
assert_equal(parser.rest, "^n:ds[4U")
|
542
|
+
end
|
543
|
+
|
544
|
+
def test_upgrade_websocket_hybi_09
|
545
|
+
parser = HTTPTools::Parser.new
|
546
|
+
method, path, headers = nil
|
547
|
+
|
548
|
+
parser.add_listener(:header) do
|
549
|
+
method = parser.request_method
|
550
|
+
path = parser.path_info
|
551
|
+
headers = parser.header
|
552
|
+
end
|
553
|
+
|
554
|
+
parser << "GET /chat HTTP/1.1\r\n"
|
555
|
+
parser << "Host: server.example.com\r\n"
|
556
|
+
parser << "Upgrade: websocket\r\n"
|
557
|
+
parser << "Connection: Upgrade\r\n"
|
558
|
+
parser << "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
|
559
|
+
parser << "Sec-WebSocket-Origin: http://example.com\r\n"
|
560
|
+
parser << "Sec-WebSocket-Protocol: chat, superchat\r\n"
|
561
|
+
parser << "Sec-WebSocket-Version: 8\r\n\r\n"
|
562
|
+
|
563
|
+
assert_equal("GET", method)
|
564
|
+
assert_equal("/chat", path)
|
565
|
+
assert_equal({
|
566
|
+
"Host" => "server.example.com",
|
567
|
+
"Upgrade" => "websocket",
|
568
|
+
"Connection" => "Upgrade",
|
569
|
+
"Sec-WebSocket-Key" => "dGhlIHNhbXBsZSBub25jZQ==",
|
570
|
+
"Sec-WebSocket-Origin" => "http://example.com",
|
571
|
+
"Sec-WebSocket-Protocol" => "chat, superchat",
|
572
|
+
"Sec-WebSocket-Version" => "8"}, headers)
|
573
|
+
assert(parser.finished?, "Parser should be finished.")
|
574
|
+
assert_equal(parser.rest, "")
|
575
|
+
end
|
576
|
+
|
461
577
|
def test_env
|
462
578
|
parser = HTTPTools::Parser.new
|
463
579
|
env = nil
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# encoding: utf-8
|
1
2
|
base = File.expand_path(File.dirname(__FILE__) + '/../../lib')
|
2
3
|
require base + '/http_tools'
|
3
4
|
require 'test/unit'
|
@@ -724,6 +725,76 @@ class ParserResponseTest < Test::Unit::TestCase
|
|
724
725
|
assert(parser.finished?, "parser should be finished")
|
725
726
|
end
|
726
727
|
|
728
|
+
def test_max_chunk_size
|
729
|
+
parser = HTTPTools::Parser.new
|
730
|
+
parser.max_chunk_size = 1024
|
731
|
+
|
732
|
+
parser << "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n"
|
733
|
+
|
734
|
+
assert_nothing_raised(HTTPTools::ParseError) {parser << "1\r\na\r\n"}
|
735
|
+
assert_nothing_raised(HTTPTools::ParseError) do
|
736
|
+
parser << "400\r\n#{"a" * 1024}\r\n"
|
737
|
+
end
|
738
|
+
|
739
|
+
assert_raise(HTTPTools::ParseError) {parser << "401\r\n"}
|
740
|
+
end
|
741
|
+
|
742
|
+
def test_upgrade_websocket_hixie_76
|
743
|
+
parser = HTTPTools::Parser.new
|
744
|
+
code, message, headers = nil
|
745
|
+
|
746
|
+
parser.add_listener(:header) do
|
747
|
+
code = parser.status_code
|
748
|
+
message = parser.message
|
749
|
+
headers = parser.header
|
750
|
+
end
|
751
|
+
|
752
|
+
parser << "HTTP/1.1 101 WebSocket Protocol Handshake\r\n"
|
753
|
+
parser << "Upgrade: WebSocket\r\n"
|
754
|
+
parser << "Connection: Upgrade\r\n"
|
755
|
+
parser << "Sec-WebSocket-Origin: http://example.com\r\n"
|
756
|
+
parser << "Sec-WebSocket-Location: ws://example.com/demo\r\n"
|
757
|
+
parser << "Sec-WebSocket-Protocol: sample\r\n\r\n8jKS'y:G*Co,Wxa-"
|
758
|
+
|
759
|
+
assert_equal(101, code)
|
760
|
+
assert_equal("WebSocket Protocol Handshake", message)
|
761
|
+
assert_equal({
|
762
|
+
"Upgrade" => "WebSocket",
|
763
|
+
"Connection" => "Upgrade",
|
764
|
+
"Sec-WebSocket-Origin" => "http://example.com",
|
765
|
+
"Sec-WebSocket-Location" => "ws://example.com/demo",
|
766
|
+
"Sec-WebSocket-Protocol" => "sample"}, headers)
|
767
|
+
assert(parser.finished?, "Parser should be finished.")
|
768
|
+
assert_equal(parser.rest, "8jKS'y:G*Co,Wxa-")
|
769
|
+
end
|
770
|
+
|
771
|
+
def test_upgrade_websocket_hybi_09
|
772
|
+
parser = HTTPTools::Parser.new
|
773
|
+
code, message, headers = nil
|
774
|
+
|
775
|
+
parser.add_listener(:header) do
|
776
|
+
code = parser.status_code
|
777
|
+
message = parser.message
|
778
|
+
headers = parser.header
|
779
|
+
end
|
780
|
+
|
781
|
+
parser << "HTTP/1.1 101 Switching Protocols\r\n"
|
782
|
+
parser << "Upgrade: websocket\r\n"
|
783
|
+
parser << "Connection: Upgrade\r\n"
|
784
|
+
parser << "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
|
785
|
+
parser << "Sec-WebSocket-Protocol: chat\r\n\r\n"
|
786
|
+
|
787
|
+
assert_equal(101, code)
|
788
|
+
assert_equal("Switching Protocols", message)
|
789
|
+
assert_equal({
|
790
|
+
"Upgrade" => "websocket",
|
791
|
+
"Connection" => "Upgrade",
|
792
|
+
"Sec-WebSocket-Accept" => "s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",
|
793
|
+
"Sec-WebSocket-Protocol" => "chat"}, headers)
|
794
|
+
assert(parser.finished?, "Parser should be finished.")
|
795
|
+
assert_equal(parser.rest, "")
|
796
|
+
end
|
797
|
+
|
727
798
|
def test_html_body_only_not_allowed
|
728
799
|
parser = HTTPTools::Parser.new
|
729
800
|
|
@@ -789,6 +860,20 @@ class ParserResponseTest < Test::Unit::TestCase
|
|
789
860
|
assert_nil(parser.body)
|
790
861
|
end
|
791
862
|
|
863
|
+
def test_header_query
|
864
|
+
parser = HTTPTools::Parser.new
|
865
|
+
|
866
|
+
parser << "HTTP/1.1 200 OK\r\n"
|
867
|
+
assert(!parser.header?, "Header not yet done.")
|
868
|
+
|
869
|
+
parser << "Content-Length: 20\r\n"
|
870
|
+
assert(!parser.header?, "Header not yet done.")
|
871
|
+
|
872
|
+
parser << "\r\n"
|
873
|
+
|
874
|
+
assert(parser.header?, "Header should be done.")
|
875
|
+
end
|
876
|
+
|
792
877
|
def test_finished
|
793
878
|
parser = HTTPTools::Parser.new
|
794
879
|
code, message, remainder = nil
|
@@ -1052,4 +1137,4 @@ class ParserResponseTest < Test::Unit::TestCase
|
|
1052
1137
|
assert_raise(HTTPTools::EmptyMessageError) {parser.finish}
|
1053
1138
|
end
|
1054
1139
|
|
1055
|
-
end
|
1140
|
+
end
|
data/test/runner.rb
CHANGED
@@ -1 +1,10 @@
|
|
1
|
-
|
1
|
+
puts
|
2
|
+
if defined? RUBY_DESCRIPTION
|
3
|
+
puts RUBY_DESCRIPTION
|
4
|
+
elsif defined? RUBY_ENGINE
|
5
|
+
puts "#{RUBY_ENGINE} #{RUBY_VERSION} (#{RELEASE_DATE} patchlevel #{RUBY_PATCHLEVEL}) [#{RUBY_PLATFORM}]"
|
6
|
+
else
|
7
|
+
puts "ruby #{RUBY_VERSION} (#{RELEASE_DATE} patchlevel #{RUBY_PATCHLEVEL}) [#{RUBY_PLATFORM}]"
|
8
|
+
end
|
9
|
+
|
10
|
+
Dir["**/*_test.rb"].each {|test| require File.expand_path(test)}
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: http_tools
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 13
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 4
|
9
|
-
-
|
10
|
-
version: 0.4.
|
9
|
+
- 1
|
10
|
+
version: 0.4.1
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Matthew Sadler
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-06-
|
18
|
+
date: 2011-06-27 00:00:00 +01:00
|
19
19
|
default_executable:
|
20
20
|
dependencies: []
|
21
21
|
|
@@ -50,6 +50,8 @@ files:
|
|
50
50
|
- profile/parser/response_profile.rb
|
51
51
|
- example/http_client.rb
|
52
52
|
- example/http_server.rb
|
53
|
+
- example/websocket_client.rb
|
54
|
+
- example/websocket_server.rb
|
53
55
|
- README.rdoc
|
54
56
|
has_rdoc: true
|
55
57
|
homepage: http://github.com/matsadler/http_tools
|