cool.io-websocket 0.1.4

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