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