faye-websocket 0.1.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of faye-websocket might be problematic. Click here for more details.

@@ -0,0 +1,146 @@
1
+ # encoding=utf-8
2
+
3
+ require "spec_helper"
4
+
5
+ WebSocketSteps = EM::RSpec.async_steps do
6
+ def server(port, secure, &callback)
7
+ @server = EchoServer.new
8
+ @server.listen(port, secure)
9
+ @port = port
10
+ EM.add_timer(0.1, &callback)
11
+ end
12
+
13
+ def stop(&callback)
14
+ @server.stop
15
+ EM.next_tick(&callback)
16
+ end
17
+
18
+ def open_socket(url, &callback)
19
+ done = false
20
+
21
+ resume = lambda do |open|
22
+ unless done
23
+ done = true
24
+ @open = open
25
+ callback.call
26
+ end
27
+ end
28
+
29
+ @ws = Faye::WebSocket::Client.new(url)
30
+
31
+ @ws.onopen = lambda { |e| resume.call(true) }
32
+ @ws.onclose = lambda { |e| resume.call(false) }
33
+ end
34
+
35
+ def close_socket(&callback)
36
+ @ws.onclose = lambda do |e|
37
+ @open = false
38
+ callback.call
39
+ end
40
+ @ws.close
41
+ end
42
+
43
+ def check_open(&callback)
44
+ @open.should == true
45
+ callback.call
46
+ end
47
+
48
+ def check_closed(&callback)
49
+ @open.should == false
50
+ callback.call
51
+ end
52
+
53
+ def listen_for_message(&callback)
54
+ @ws.onmessage = lambda { |e| @message = e.data }
55
+ callback.call
56
+ end
57
+
58
+ def send_message(&callback)
59
+ @ws.send("I expect this to be echoed")
60
+ EM.add_timer(0.1, &callback)
61
+ end
62
+
63
+ def check_response(&callback)
64
+ @message.should == "I expect this to be echoed"
65
+ callback.call
66
+ end
67
+
68
+ def check_no_response(&callback)
69
+ @message.should == nil
70
+ callback.call
71
+ end
72
+ end
73
+
74
+ describe Faye::WebSocket::Client do
75
+ include WebSocketSteps
76
+
77
+ let(:plain_text_url) { "ws://0.0.0.0:8000/" }
78
+ let(:secure_url) { "wss://0.0.0.0:8000/" }
79
+
80
+ before do
81
+ Thread.new { EM.run }
82
+ sleep(0.1) until EM.reactor_running?
83
+ end
84
+
85
+ shared_examples_for "socket client" do
86
+ it "can open a connection" do
87
+ open_socket(socket_url)
88
+ check_open
89
+ end
90
+
91
+ it "cannot open a connection to the wrong host" do
92
+ open_socket(blocked_url)
93
+ check_closed
94
+ end
95
+
96
+ it "can close the connection" do
97
+ open_socket(socket_url)
98
+ close_socket
99
+ check_closed
100
+ end
101
+
102
+ describe "in the OPEN state" do
103
+ before { open_socket(socket_url) }
104
+
105
+ it "can send and receive messages" do
106
+ listen_for_message
107
+ send_message
108
+ check_response
109
+ end
110
+ end
111
+
112
+ describe "in the CLOSED state" do
113
+ before do
114
+ open_socket(socket_url)
115
+ close_socket
116
+ end
117
+
118
+ it "cannot send and receive messages" do
119
+ listen_for_message
120
+ send_message
121
+ check_no_response
122
+ end
123
+ end
124
+ end
125
+
126
+ describe "with a plain-text server" do
127
+ let(:socket_url) { plain_text_url }
128
+ let(:blocked_url) { secure_url }
129
+
130
+ before { server 8000, false }
131
+ after { sync ; stop }
132
+
133
+ it_should_behave_like "socket client"
134
+ end
135
+
136
+ describe "with a secure server" do
137
+ let(:socket_url) { secure_url }
138
+ let(:blocked_url) { plain_text_url }
139
+
140
+ before { server 8000, true }
141
+ after { sync ; stop }
142
+
143
+ it_should_behave_like "socket client"
144
+ end
145
+ end
146
+
@@ -0,0 +1,41 @@
1
+ # encoding=utf-8
2
+
3
+ require "spec_helper"
4
+
5
+ describe Faye::WebSocket::Draft75Parser do
6
+ include EncodingHelper
7
+
8
+ before do
9
+ @web_socket = mock Faye::WebSocket
10
+ @parser = Faye::WebSocket::Draft75Parser.new(@web_socket)
11
+ end
12
+
13
+ describe :parse do
14
+ it "parses text frames" do
15
+ @web_socket.should_receive(:receive).with("Hello")
16
+ parse [0x00, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0xff]
17
+ end
18
+
19
+ it "parses multibyte text frames" do
20
+ @web_socket.should_receive(:receive).with(encode "Apple = ")
21
+ parse [0x00, 0x41, 0x70, 0x70, 0x6c, 0x65, 0x20, 0x3d, 0x20, 0xef, 0xa3, 0xbf, 0xff]
22
+ end
23
+
24
+ it "parses fragmented frames" do
25
+ @web_socket.should_receive(:receive).with("Hello")
26
+ parse [0x00, 0x48, 0x65, 0x6c]
27
+ parse [0x6c, 0x6f, 0xff]
28
+ end
29
+ end
30
+
31
+ describe :frame do
32
+ it "returns the given string formatted as a WebSocket frame" do
33
+ bytes(@parser.frame "Hello").should == [0x00, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0xff]
34
+ end
35
+
36
+ it "encodes multibyte characters correctly" do
37
+ message = encode "Apple = "
38
+ bytes(@parser.frame message).should == [0x00, 0x41, 0x70, 0x70, 0x6c, 0x65, 0x20, 0x3d, 0x20, 0xef, 0xa3, 0xbf, 0xff]
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,145 @@
1
+ # encoding=utf-8
2
+
3
+ require "spec_helper"
4
+
5
+ describe Faye::WebSocket::Protocol8Parser do
6
+ include EncodingHelper
7
+
8
+ before do
9
+ @web_socket = mock Faye::WebSocket
10
+ @parser = Faye::WebSocket::Protocol8Parser.new(@web_socket)
11
+ end
12
+
13
+ describe :parse do
14
+ let(:mask) { (1..4).map { rand 255 } }
15
+
16
+ def mask_message(*bytes)
17
+ output = []
18
+ bytes.each_with_index do |byte, i|
19
+ output[i] = byte ^ mask[i % 4]
20
+ end
21
+ output
22
+ end
23
+
24
+ it "parses unmasked text frames" do
25
+ @web_socket.should_receive(:receive).with("Hello")
26
+ parse [0x81, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f]
27
+ end
28
+
29
+ it "parses empty text frames" do
30
+ @web_socket.should_receive(:receive).with("")
31
+ parse [0x81, 0x00]
32
+ end
33
+
34
+ it "parses fragmented text frames" do
35
+ @web_socket.should_receive(:receive).with("Hello")
36
+ parse [0x01, 0x03, 0x48, 0x65, 0x6c]
37
+ parse [0x80, 0x02, 0x6c, 0x6f]
38
+ end
39
+
40
+ it "parses masked text frames" do
41
+ @web_socket.should_receive(:receive).with("Hello")
42
+ parse [0x81, 0x85] + mask + mask_message(0x48, 0x65, 0x6c, 0x6c, 0x6f)
43
+ end
44
+
45
+ it "parses masked empty text frames" do
46
+ @web_socket.should_receive(:receive).with("")
47
+ parse [0x81, 0x80] + mask + mask_message()
48
+ end
49
+
50
+ it "parses masked fragmented text frames" do
51
+ @web_socket.should_receive(:receive).with("Hello")
52
+ parse [0x01, 0x81] + mask + mask_message(0x48)
53
+ parse [0x80, 0x84] + mask + mask_message(0x65, 0x6c, 0x6c, 0x6f)
54
+ end
55
+
56
+ it "closes the socket if the frame has an unrecognized opcode" do
57
+ @web_socket.should_receive(:close).with(1002, nil, false)
58
+ parse [0x83, 0x00]
59
+ end
60
+
61
+ it "closes the socket if a close frame is received" do
62
+ @web_socket.should_receive(:close).with(1000, "Hello", false)
63
+ parse [0x88, 0x07, 0x03, 0xe8, 0x48, 0x65, 0x6c, 0x6c, 0x6f]
64
+ end
65
+
66
+ it "parses unmasked multibyte text frames" do
67
+ @web_socket.should_receive(:receive).with(encode "Apple = ")
68
+ parse [0x81, 0x0b, 0x41, 0x70, 0x70, 0x6c, 0x65, 0x20, 0x3d, 0x20, 0xef, 0xa3, 0xbf]
69
+ end
70
+
71
+ it "parses fragmented multibyte text frames" do
72
+ @web_socket.should_receive(:receive).with(encode "Apple = ")
73
+ parse [0x01, 0x0a, 0x41, 0x70, 0x70, 0x6c, 0x65, 0x20, 0x3d, 0x20, 0xef, 0xa3]
74
+ parse [0x80, 0x01, 0xbf]
75
+ end
76
+
77
+ it "parses masked multibyte text frames" do
78
+ @web_socket.should_receive(:receive).with(encode "Apple = ")
79
+ parse [0x81, 0x8b] + mask + mask_message(0x41, 0x70, 0x70, 0x6c, 0x65, 0x20, 0x3d, 0x20, 0xef, 0xa3, 0xbf)
80
+ end
81
+
82
+ it "parses masked fragmented multibyte text frames" do
83
+ @web_socket.should_receive(:receive).with(encode "Apple = ")
84
+ parse [0x01, 0x8a] + mask + mask_message(0x41, 0x70, 0x70, 0x6c, 0x65, 0x20, 0x3d, 0x20, 0xef, 0xa3)
85
+ parse [0x80, 0x81] + mask + mask_message(0xbf)
86
+ end
87
+
88
+ it "parses unmasked medium-length text frames" do
89
+ @web_socket.should_receive(:receive).with("Hello" * 40)
90
+ parse [0x81, 0x7e, 0x00, 0xc8] + [0x48, 0x65, 0x6c, 0x6c, 0x6f] * 40
91
+ end
92
+
93
+ it "parses masked medium-length text frames" do
94
+ @web_socket.should_receive(:receive).with("Hello" * 40)
95
+ parse [0x81, 0xfe, 0x00, 0xc8] + mask + mask_message(*([0x48, 0x65, 0x6c, 0x6c, 0x6f] * 40))
96
+ end
97
+
98
+ it "replies to pings with a pong" do
99
+ @web_socket.should_receive(:send).with([0x4f, 0x48, 0x41, 0x49], :pong)
100
+ parse [0x89, 0x04, 0x4f, 0x48, 0x41, 0x49]
101
+ end
102
+ end
103
+
104
+ describe :frame do
105
+ it "returns the given string formatted as a WebSocket frame" do
106
+ bytes(@parser.frame "Hello").should == [0x81, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f]
107
+ end
108
+
109
+ it "encodes multibyte characters correctly" do
110
+ message = encode "Apple = "
111
+ bytes(@parser.frame message).should == [0x81, 0x0b, 0x41, 0x70, 0x70, 0x6c, 0x65, 0x20, 0x3d, 0x20, 0xef, 0xa3, 0xbf]
112
+ end
113
+
114
+ it "encodes medium-length strings using extra length bytes" do
115
+ message = "Hello" * 40
116
+ bytes(@parser.frame message).should == [0x81, 0x7e, 0x00, 0xc8] + [0x48, 0x65, 0x6c, 0x6c, 0x6f] * 40
117
+ end
118
+
119
+ it "encodes long strings using extra length bytes" do
120
+ message = "Hello" * 13108
121
+ bytes(@parser.frame message).should == [0x81, 0x7f] +
122
+ [0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x04] +
123
+ [0x48, 0x65, 0x6c, 0x6c, 0x6f] * 13108
124
+ end
125
+
126
+ it "encodes close frames with an error code" do
127
+ frame = @parser.frame "Hello", :close, 1002
128
+ bytes(frame).should == [0x88, 0x07, 0x03, 0xea, 0x48, 0x65, 0x6c, 0x6c, 0x6f]
129
+ end
130
+
131
+ it "encodes pong frames" do
132
+ bytes(@parser.frame '', :pong).should == [0x8a, 0x00]
133
+ end
134
+ end
135
+
136
+ describe :utf8 do
137
+ it "detects valid UTF-8" do
138
+ Faye::WebSocket.valid_utf8?( [72, 101, 108, 108, 111, 45, 194, 181, 64, 195, 159, 195, 182, 195, 164, 195, 188, 195, 160, 195, 161, 45, 85, 84, 70, 45, 56, 33, 33] ).should be_true
139
+ end
140
+
141
+ it "detects invalid UTF-8" do
142
+ Faye::WebSocket.valid_utf8?( [206, 186, 225, 189, 185, 207, 131, 206, 188, 206, 181, 237, 160, 128, 101, 100, 105, 116, 101, 100] ).should be_false
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,15 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIICZTCCAc4CCQDxyrJZrFA0vjANBgkqhkiG9w0BAQUFADB3MQswCQYDVQQGEwJV
3
+ SzEPMA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDTALBgNVBAoTBEZh
4
+ eWUxFTATBgNVBAMTDEphbWVzIENvZ2xhbjEgMB4GCSqGSIb3DQEJARYRamNvZ2xh
5
+ bkBnbWFpbC5jb20wHhcNMTEwODMwMTIzOTM2WhcNMTIwODI5MTIzOTM2WjB3MQsw
6
+ CQYDVQQGEwJVSzEPMA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDTAL
7
+ BgNVBAoTBEZheWUxFTATBgNVBAMTDEphbWVzIENvZ2xhbjEgMB4GCSqGSIb3DQEJ
8
+ ARYRamNvZ2xhbkBnbWFpbC5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGB
9
+ AMDjU5fAK7fvUCZIYHcGXDZD/m9bY+B/UcwGcowk0hMQGYNlLKrpiK7xXBmZpDb6
10
+ r8n+7L/epBeSumbRIm4TDzeNHhuQGYLIeGQy7JNLoPBr6GxubjuJhKOOBnCqcupR
11
+ CLGG7Zw5oL4UvtZVH6kL9XnjyokQQbxxeoV9DqtqOaHHAgMBAAEwDQYJKoZIhvcN
12
+ AQEFBQADgYEAvQjSpzE1ahaeH1CmbLwckTxvWMZfxcZOrxTruK1po3cNnDOjGqFQ
13
+ KEkNj3K5WfwTBD4QgUdYDykhDX2m6HaMz4JEbgrwQv8M8FiswIA3dyGsbOifOk8H
14
+ r3GPNKMzm4o6vrn6RGOpt9q6bsWUBUHfNpP93uU2C9QEwDua3cFjDA0=
15
+ -----END CERTIFICATE-----
@@ -0,0 +1,15 @@
1
+ -----BEGIN RSA PRIVATE KEY-----
2
+ MIICXAIBAAKBgQDA41OXwCu371AmSGB3Blw2Q/5vW2Pgf1HMBnKMJNITEBmDZSyq
3
+ 6Yiu8VwZmaQ2+q/J/uy/3qQXkrpm0SJuEw83jR4bkBmCyHhkMuyTS6Dwa+hsbm47
4
+ iYSjjgZwqnLqUQixhu2cOaC+FL7WVR+pC/V548qJEEG8cXqFfQ6rajmhxwIDAQAB
5
+ AoGABlk1DiCQD8y7mZb2PdSiwlJ4lFewsNnf6lQn/v7TPzdfb5ir4LAxBHkDLACH
6
+ jBuyH3bZefMs+W2l3u5xMKhF7uJqYcUlJdH2UwRfNG54Hn4SGAjQOK3ONer99sUf
7
+ USlsWSX1HjAAFMCBwUfKxMZA3VNQfYKTPdm0jSVf85kHO1ECQQD3s6ksm3QpfD0L
8
+ eG9EoDrqmwnEfpKoWPpz1O0i5tY9VcmhmLwS5Zpd7lB1qjTqzZk4RygU73T/BseJ
9
+ azehIHK5AkEAx1mSXt+ec8RfzVi/io6oqi2vOcACXRbOG4NQmqUWPnumdwsJjsjR
10
+ RzEoDFC2lu6448p9sgEq+CkbmgVeiyp4fwJAQnmgySve/NMuvslPcyddKGD7OhSN
11
+ 30ghzrwx98/jZwqC1i9bKeccimDOjwVitjD/Ea9m/ldVGqwDGMoBX+iJYQJAEIOO
12
+ CYfyw1pQKV2huGOq+zX/nwQV7go2lrbhFX55gkGR/6iNaSOfmosq6yJAje5GqLAc
13
+ i4NnQNl+7NpnA5ZIFwJBAI1+OsZyjbRI99pYkTdOpa5IPlIb3j3JbSfjAWHLxlRY
14
+ 0HLvN3Q1mE9kbB+uKH6syF/S7nALgsLgq7eHYvIaE/A=
15
+ -----END RSA PRIVATE KEY-----
@@ -0,0 +1,50 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+ require File.expand_path('../../lib/faye/websocket', __FILE__)
4
+ require File.expand_path('../../vendor/em-rspec/lib/em-rspec', __FILE__)
5
+
6
+ Thin::Logging.silent = true
7
+
8
+ module EncodingHelper
9
+ def encode(message)
10
+ message.respond_to?(:force_encoding) ?
11
+ message.force_encoding("UTF-8") :
12
+ message
13
+ end
14
+
15
+ def bytes(string)
16
+ string.bytes.to_a
17
+ end
18
+
19
+ def parse(bytes)
20
+ @parser.parse(bytes.pack('C*'))
21
+ end
22
+ end
23
+
24
+ class EchoServer
25
+ def call(env)
26
+ socket = Faye::WebSocket.new(env)
27
+ socket.onmessage = lambda do |event|
28
+ socket.send(event.data)
29
+ end
30
+ [-1, {}, []]
31
+ end
32
+
33
+ def listen(port, ssl = false)
34
+ Rack::Handler.get('thin').run(self, :Port => port) do |s|
35
+ if ssl
36
+ s.ssl = true
37
+ s.ssl_options = {
38
+ :private_key_file => File.expand_path('../server.key', __FILE__),
39
+ :cert_chain_file => File.expand_path('../server.crt', __FILE__)
40
+ }
41
+ end
42
+ @server = s
43
+ end
44
+ end
45
+
46
+ def stop
47
+ @server.stop
48
+ end
49
+ end
50
+
metadata ADDED
@@ -0,0 +1,109 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: faye-websocket
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - James Coglan
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-11-28 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: eventmachine
16
+ requirement: &2152439260 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 0.12.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *2152439260
25
+ - !ruby/object:Gem::Dependency
26
+ name: thin
27
+ requirement: &2152438100 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ~>
31
+ - !ruby/object:Gem::Version
32
+ version: '1.2'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *2152438100
36
+ - !ruby/object:Gem::Dependency
37
+ name: rspec
38
+ requirement: &2152437380 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ version: 2.5.0
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *2152437380
47
+ - !ruby/object:Gem::Dependency
48
+ name: rack
49
+ requirement: &2152436360 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *2152436360
58
+ description:
59
+ email: jcoglan@gmail.com
60
+ executables: []
61
+ extensions: []
62
+ extra_rdoc_files:
63
+ - README.rdoc
64
+ files:
65
+ - README.rdoc
66
+ - examples/autobahn_client.rb
67
+ - examples/client.rb
68
+ - examples/index.html
69
+ - examples/server.rb
70
+ - lib/faye/thin_extensions.rb
71
+ - lib/faye/websocket/api.rb
72
+ - lib/faye/websocket/client.rb
73
+ - lib/faye/websocket/draft75_parser.rb
74
+ - lib/faye/websocket/draft76_parser.rb
75
+ - lib/faye/websocket/protocol8_parser.rb
76
+ - lib/faye/websocket.rb
77
+ - spec/faye/websocket/client_spec.rb
78
+ - spec/faye/websocket/draft75_parser_spec.rb
79
+ - spec/faye/websocket/protocol8_parser_spec.rb
80
+ - spec/server.crt
81
+ - spec/server.key
82
+ - spec/spec_helper.rb
83
+ homepage: http://github.com/jcoglan/faye-websocket-ruby
84
+ licenses: []
85
+ post_install_message:
86
+ rdoc_options:
87
+ - --main
88
+ - README.rdoc
89
+ require_paths:
90
+ - lib
91
+ required_ruby_version: !ruby/object:Gem::Requirement
92
+ none: false
93
+ requirements:
94
+ - - ! '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ required_rubygems_version: !ruby/object:Gem::Requirement
98
+ none: false
99
+ requirements:
100
+ - - ! '>='
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ requirements: []
104
+ rubyforge_project:
105
+ rubygems_version: 1.8.10
106
+ signing_key:
107
+ specification_version: 3
108
+ summary: Robust general-purpose WebSocket server and client
109
+ test_files: []