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.
@@ -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 |header|
25
- puts parser.status_code + " " + parser.request_method
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
@@ -12,8 +12,8 @@ module HTTPTools
12
12
  #
13
13
  # Example:
14
14
  # parser = HTTPTools::Parser.new
15
- # parser.on(:header) do |header|
16
- # puts parser.status_code + " " + parser.request_method
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
@@ -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'
@@ -34,4 +35,4 @@ class URLEncodingTest < Test::Unit::TestCase
34
35
  assert_equal("À", result)
35
36
  end
36
37
 
37
- end
38
+ end
@@ -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
- # may fail under Ruby < 1.9 because of the unpredictable ordering of Hash
11
- assert_equal("foo=bar&baz=qux", result)
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
- # may fail under Ruby < 1.9 because of the unpredictable ordering of Hash
18
- assert_equal("lang=en&lang=fr&q=foo&q=bar", result)
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
@@ -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
@@ -1 +1,10 @@
1
- Dir["**/*_test.rb"].each {|test| require test}
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: 15
4
+ hash: 13
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 4
9
- - 0
10
- version: 0.4.0
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-12 00:00:00 +01:00
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