faye-websocket 0.1.0

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.

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: []