cool.io-websocket 0.1.4

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/examples/rpc ADDED
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ fork {
4
+ load 'rpc.rb'
5
+ exit 0
6
+ }
7
+
8
+ require 'rubygems'
9
+ require 'sinatra'
10
+ require 'msgpack/rpc'
11
+
12
+ rpc_port = 18800
13
+ $ws = MessagePack::RPC::Client.new('127.0.0.1', rpc_port)
14
+
15
+ get '/' do
16
+ erb :rpc
17
+ end
18
+
19
+ post '/push' do
20
+ data = {'Hello'=>'World!', 'data' => params}
21
+ $ws.call(:push_data, data)
22
+ redirect '/', 303
23
+ end
24
+
25
+ set :port, 8080
26
+
data/examples/rpc.rb ADDED
@@ -0,0 +1,47 @@
1
+ # RPC push
2
+ # This program receives messages.
3
+ # See ./rpc file which sends messages to this program.
4
+
5
+ require 'rubygems'
6
+ require 'cool.io-websocket'
7
+ require 'msgpack/rpc'
8
+ require 'json'
9
+
10
+ $sockets = {}
11
+
12
+ class MyConnection < Cool.io::WebSocket
13
+ def on_open
14
+ puts "WebSocket opened from '#{peeraddr[2]}': request=#{request.inspect}"
15
+ $sockets[self] = self
16
+ end
17
+
18
+ def on_close
19
+ puts "WebSocket closed"
20
+ $sockets.delete(self)
21
+ end
22
+ end
23
+
24
+ class RPCServer
25
+ def push_data(data)
26
+ $sockets.each_key {|sock|
27
+ sock.send_message(data.to_json)
28
+ }
29
+ nil
30
+ end
31
+ end
32
+
33
+ host = '0.0.0.0'
34
+ port = ARGV[0] || 8081
35
+
36
+ rpc_port = 18800
37
+
38
+ loop = Cool.io::Loop.default
39
+
40
+ ws = Cool.io::WebSocketServer.new(host, port, MyConnection)
41
+ ws.attach(loop)
42
+
43
+ rpc = MessagePack::RPC::Server.new(loop)
44
+ rpc.listen('127.0.0.1', rpc_port, RPCServer.new)
45
+
46
+ loop.run
47
+
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ fork {
4
+ load 'shoutchat.rb'
5
+ exit 0
6
+ }
7
+
8
+ require 'webrick'
9
+
10
+ server = WEBrick::HTTPServer.new({
11
+ :Port => 8080,
12
+ :BindAddress => '127.0.0.1',
13
+ :DocumentRoot => File.dirname(__FILE__)+'/public'})
14
+
15
+ trap('INT') { server.shutdown }
16
+
17
+ server.start
18
+
@@ -0,0 +1,79 @@
1
+ # Publisher/Subscriber-style message routing
2
+
3
+ require 'rubygems'
4
+ require 'cool.io-websocket'
5
+ require 'json'
6
+
7
+ class PubSub
8
+ def initialize
9
+ @subscriber = {}
10
+ @seqid = 0
11
+ end
12
+
13
+ def subscribe(&block)
14
+ sid = @seqid += 1
15
+ @subscriber[sid] = block
16
+ return sid
17
+ end
18
+
19
+ def unsubscribe(key)
20
+ @subscriber.delete(key)
21
+ end
22
+
23
+ def publish(data)
24
+ @subscriber.reject! {|sid,block|
25
+ begin
26
+ block.call(data)
27
+ false
28
+ rescue
29
+ true
30
+ end
31
+ }
32
+ end
33
+
34
+ def size
35
+ @subscriber.size
36
+ end
37
+ end
38
+
39
+ $pubsub = PubSub.new
40
+ $record = []
41
+
42
+ class ShoutChatConnection < Cool.io::WebSocket
43
+ def on_open
44
+ @host = peeraddr[2]
45
+ puts "connection opened: <#{@host}>"
46
+
47
+ @sid = $pubsub.subscribe {|data|
48
+ send_message data
49
+ }
50
+ $pubsub.publish(["count", $pubsub.size].to_json)
51
+ $record.each {|data| send_message data }
52
+ end
53
+
54
+ def on_message(data)
55
+ puts "broadcasting: <#{@host}> '#{data}'"
56
+
57
+ $pubsub.publish(data)
58
+ $record.push(data)
59
+ $record.shift while $record.size > 20
60
+ end
61
+
62
+ def on_close
63
+ puts "connection closed: <#{@host}>"
64
+
65
+ $pubsub.unsubscribe(@sid)
66
+ $pubsub.publish(["count", $pubsub.size].to_json)
67
+ end
68
+ end
69
+
70
+ host = '0.0.0.0'
71
+ port = ARGV[0] || 8081
72
+
73
+ server = Cool.io::WebSocketServer.new(host, port, ShoutChatConnection)
74
+ server.attach(Cool.io::Loop.default)
75
+
76
+ puts "start on #{host}:#{port}"
77
+
78
+ Cool.io::Loop.default.run
79
+
@@ -0,0 +1,67 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta content="text/css" http-equiv="content-style-type" />
6
+ <meta content="text/javascript" http-equiv="content-script-type" />
7
+
8
+ <script type="text/javascript" src='js/jquery.min.js'></script>
9
+ <script type="text/javascript" src='js/swfobject.js'></script>
10
+ <script type="text/javascript" src='js/FABridge.js'></script>
11
+ <script type="text/javascript" src='js/web_socket.js'></script>
12
+ <script type="text/javascript" src='js/json2.js'></script>
13
+ <link rel="stylesheet" type="text/css" href="echo.css" />
14
+
15
+ <title>Cool.io-WebSocket Demo: Echo server</title>
16
+
17
+ <script>
18
+ WS_URL = "ws://localhost:8081";
19
+ WEB_SOCKET_SWF_LOCATION = "js/WebSocketMain.swf";
20
+
21
+ var global = this;
22
+
23
+ $(document).ready(function(){
24
+
25
+ function debug(message) {
26
+ html = "<p><span class='time'>"+new Date()+"</span>"+message+"</p>"
27
+ $("#debug").append(html);
28
+ }
29
+
30
+ debug("connecting to "+WS_URL+"...");
31
+ ws = new WebSocket(WS_URL);
32
+
33
+ ws.onopen = function() {
34
+ debug("connected.");
35
+
36
+ text = "client: hello";
37
+ ws.send(text);
38
+
39
+ debug("message sent: "+text);
40
+ }
41
+
42
+ ws.onclose = function() {
43
+ debug("disconnected...");
44
+ }
45
+
46
+ ws.onerror = function(msg) {
47
+ debug("failed to connect:"+msg);
48
+ }
49
+
50
+ ws.onmessage = function(event) {
51
+ debug("message received: "+event.data);
52
+ $("#message").append("<p>"+event.data+"</p>");
53
+ }
54
+ });
55
+ </script>
56
+ </head>
57
+ <body>
58
+ <div>
59
+ <form action="/push" target="_blank" method="post">
60
+ <input type="text" name="text" id="text" value="text..." />
61
+ <input type="submit" value="submit" />
62
+ </form>
63
+ </div>
64
+
65
+ <div id="message"></div>
66
+ <div id="debug"></div>
67
+ </body>
@@ -0,0 +1 @@
1
+ require File.dirname(__FILE__)+'/coolio/websocket'
@@ -0,0 +1,243 @@
1
+ require 'cool.io'
2
+ require File.dirname(__FILE__)+'/websocket/spec'
3
+ require 'thin_parser'
4
+
5
+ module Coolio
6
+ class WebSocketServer < TCPServer
7
+ def initialize(host, port = nil, klass = WebSocket, *args, &block)
8
+ super
9
+ end
10
+ end
11
+
12
+ # WebSocket spec:
13
+ # http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76
14
+
15
+ class WebSocket < TCPSocket
16
+ def on_open
17
+ end
18
+
19
+ def on_message(data)
20
+ end
21
+
22
+ def on_error(reason)
23
+ end
24
+
25
+ attr_reader :request
26
+
27
+ def send_message(data)
28
+ if HAVE_ENCODING
29
+ frame = FRAME_START + data.force_encoding('UTF-8') + FRAME_END
30
+ else
31
+ frame = FRAME_START + data + FRAME_END
32
+ end
33
+ write frame
34
+ end
35
+
36
+ if "".respond_to?(:force_encoding)
37
+ HAVE_ENCODING = true
38
+ FRAME_START = "\x00".force_encoding('UTF-8')
39
+ FRAME_END = "\xFF".force_encoding('UTF-8')
40
+ else
41
+ HAVE_ENCODING = false
42
+ FRAME_START = "\x00"
43
+ FRAME_END = "\xFF"
44
+ end
45
+
46
+ #HTTP11_PRASER = Mongrel::HttpParser
47
+ HTTP11_PRASER = Thin::HttpParser
48
+
49
+ # Thin::HttpParser tries to call request['rack.input'].write(body)
50
+ class DummyIO
51
+ KEY = 'rack.input'
52
+ def write(data) end
53
+ end
54
+
55
+ def initialize(socket)
56
+ super
57
+ @state = :process_handshake
58
+ @data = ::IO::Buffer.new
59
+ @http11 = HTTP11_PRASER.new
60
+ @http11_nbytes = 0
61
+ @request = {DummyIO::KEY => DummyIO.new}
62
+ end
63
+
64
+ def on_readable
65
+ super
66
+ rescue
67
+ close
68
+ end
69
+
70
+ def on_read(data)
71
+ @data << data
72
+ dispatch
73
+ end
74
+
75
+ protected
76
+
77
+ def dispatch
78
+ while __send__(@state)
79
+ end
80
+ end
81
+
82
+ def process_handshake
83
+ return false if @data.empty?
84
+
85
+ data = @data.to_str
86
+
87
+ if data == "<policy-file-request/>\0"
88
+ write_policy_file
89
+ @state = :invalid_state
90
+ return false
91
+ end
92
+
93
+ begin
94
+ @http11_nbytes = @http11.execute(@request, data, @http11_nbytes)
95
+ rescue
96
+ on_error "invalid HTTP header, parsing fails"
97
+ @state = :invalid_state
98
+ close
99
+ end
100
+
101
+ return false unless @http11.finished?
102
+
103
+ @data.read(@http11_nbytes-1)
104
+ remove_instance_variable(:@http11)
105
+ remove_instance_variable(:@http11_nbytes)
106
+
107
+ @request.delete(DummyIO::KEY)
108
+
109
+ unless @request["REQUEST_METHOD"] == "GET"
110
+ raise RuntimeError, "Request method must be GET"
111
+ end
112
+
113
+ unless @request['HTTP_CONNECTION'] == 'Upgrade' and @request['HTTP_UPGRADE'] == 'WebSocket'
114
+ raise RequestError, "Connection and Upgrade headers required"
115
+ end
116
+
117
+ @state = :process_frame_header
118
+
119
+ version = @request['HTTP_SEC_WEBSOCKET_KEY1'] ? 76 : 75
120
+ begin
121
+ case version
122
+ when 75
123
+ extend Spec75
124
+ when 76
125
+ extend Spec76
126
+ end
127
+
128
+ if handshake
129
+ on_open
130
+ end
131
+
132
+ rescue
133
+ on_bad_request
134
+ return false
135
+ end
136
+
137
+ return true
138
+ end
139
+
140
+ def on_bad_request
141
+ write "HTTP/1.1 400 Bad request\r\n\r\n"
142
+ close
143
+ end
144
+
145
+ def process_frame_header
146
+ return false if @data.empty?
147
+
148
+ @frame_type = @data.read(1).to_i
149
+ if (@frame_type & 0x80) == 0x80
150
+ @binary_length = 0
151
+ @state = :process_binary_frame_header
152
+ else
153
+ @state = :process_text_frame
154
+ end
155
+
156
+ return true
157
+ end
158
+
159
+ def process_binary_frame_header
160
+ until @data.empty?
161
+
162
+ b = @data.read(1).to_i
163
+ b_v = b & 0x7f
164
+ @binary_length = (@binary_length<<7) | b_v
165
+
166
+ if (b & 0x80) == 0x80
167
+ if @binary_length == 0
168
+ # If the /frame type/ is 0xFF and the /length/ was 0
169
+ write "\xff\x00"
170
+ @state = :invalid_state
171
+ close
172
+ return false
173
+ end
174
+
175
+ @state = :process_binary_frame
176
+ return true
177
+ end
178
+
179
+ end
180
+ return false
181
+ end
182
+
183
+ def process_binary_frame
184
+ return false if @data.size < @binary_length
185
+
186
+ # Just discard the read bytes.
187
+ @data.read(@binary_length)
188
+
189
+ @state = :process_frame_header
190
+ return true
191
+ end
192
+
193
+ def process_text_frame
194
+ return false if @data.empty?
195
+
196
+ pos = @data.to_str.index("\xff")
197
+ if pos.nil?
198
+ return false
199
+ end
200
+
201
+ msg = @data.read(pos)
202
+ @data.read(1) # read 0xff byte
203
+
204
+ @state = :process_frame_header
205
+
206
+ if @frame_type != 0x00
207
+ # discard the data
208
+ return true
209
+ end
210
+
211
+ msg.force_encoding('UTF-8') if HAVE_ENCODING
212
+ on_message(msg)
213
+
214
+ return true
215
+ end
216
+
217
+ def write_policy_file
218
+ write %[<cross-domain-policy><allow-access-from domain="*" to-ports="*"/></cross-domain-policy>\0]
219
+ end
220
+
221
+ def ssl?
222
+ false
223
+ end
224
+
225
+ private
226
+
227
+ def invalid_state
228
+ raise RuntimeError, "invalid state"
229
+ end
230
+ end
231
+
232
+ class SSLWebSocket < WebSocket
233
+ def on_connect
234
+ extend SSL
235
+ ssl_server_start
236
+ end
237
+
238
+ def ssl?
239
+ true
240
+ end
241
+ end
242
+ end
243
+