_bushido-faye-websocket 0.4.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. data/CHANGELOG.txt +56 -0
  2. data/README.rdoc +366 -0
  3. data/examples/app.rb +50 -0
  4. data/examples/autobahn_client.rb +44 -0
  5. data/examples/client.rb +30 -0
  6. data/examples/config.ru +17 -0
  7. data/examples/haproxy.conf +21 -0
  8. data/examples/server.rb +44 -0
  9. data/examples/sse.html +39 -0
  10. data/examples/ws.html +44 -0
  11. data/ext/faye_websocket_mask/FayeWebsocketMaskService.java +61 -0
  12. data/ext/faye_websocket_mask/extconf.rb +5 -0
  13. data/ext/faye_websocket_mask/faye_websocket_mask.c +33 -0
  14. data/lib/faye/adapters/goliath.rb +47 -0
  15. data/lib/faye/adapters/rainbows.rb +32 -0
  16. data/lib/faye/adapters/rainbows_client.rb +70 -0
  17. data/lib/faye/adapters/thin.rb +62 -0
  18. data/lib/faye/eventsource.rb +124 -0
  19. data/lib/faye/websocket.rb +216 -0
  20. data/lib/faye/websocket/adapter.rb +21 -0
  21. data/lib/faye/websocket/api.rb +96 -0
  22. data/lib/faye/websocket/api/event.rb +33 -0
  23. data/lib/faye/websocket/api/event_target.rb +34 -0
  24. data/lib/faye/websocket/client.rb +84 -0
  25. data/lib/faye/websocket/draft75_parser.rb +87 -0
  26. data/lib/faye/websocket/draft76_parser.rb +84 -0
  27. data/lib/faye/websocket/hybi_parser.rb +320 -0
  28. data/lib/faye/websocket/hybi_parser/handshake.rb +78 -0
  29. data/lib/faye/websocket/hybi_parser/stream_reader.rb +29 -0
  30. data/lib/faye/websocket/utf8_match.rb +8 -0
  31. data/spec/faye/websocket/client_spec.rb +179 -0
  32. data/spec/faye/websocket/draft75_parser_examples.rb +48 -0
  33. data/spec/faye/websocket/draft75_parser_spec.rb +27 -0
  34. data/spec/faye/websocket/draft76_parser_spec.rb +34 -0
  35. data/spec/faye/websocket/hybi_parser_spec.rb +156 -0
  36. data/spec/rainbows.conf +3 -0
  37. data/spec/server.crt +15 -0
  38. data/spec/server.key +15 -0
  39. data/spec/spec_helper.rb +68 -0
  40. metadata +158 -0
@@ -0,0 +1,44 @@
1
+ require 'rubygems'
2
+ require File.expand_path('../../lib/faye/websocket', __FILE__)
3
+ require 'cgi'
4
+
5
+ EM.run {
6
+ host = 'ws://localhost:9001'
7
+ agent = "Faye (Ruby #{RUBY_VERSION})"
8
+ cases = 0
9
+ skip = []
10
+
11
+ socket = Faye::WebSocket::Client.new("#{host}/getCaseCount")
12
+
13
+ socket.onmessage = lambda do |event|
14
+ puts "Total cases to run: #{event.data}"
15
+ cases = event.data.to_i
16
+ end
17
+
18
+ socket.onclose = lambda do |event|
19
+ run_case = lambda do |n|
20
+ if n > cases
21
+ socket = Faye::WebSocket::Client.new("#{host}/updateReports?agent=#{CGI.escape agent}")
22
+ socket.onclose = lambda { |e| EM.stop }
23
+
24
+ elsif skip.include?(n)
25
+ EM.next_tick { run_case.call(n+1) }
26
+
27
+ else
28
+ puts "Running test case ##{n} ..."
29
+ socket = Faye::WebSocket::Client.new("#{host}/runCase?case=#{n}&agent=#{CGI.escape agent}")
30
+
31
+ socket.onmessage = lambda do |event|
32
+ socket.send(event.data)
33
+ end
34
+
35
+ socket.onclose = lambda do |event|
36
+ run_case.call(n + 1)
37
+ end
38
+ end
39
+ end
40
+
41
+ run_case.call(1)
42
+ end
43
+ }
44
+
@@ -0,0 +1,30 @@
1
+ require 'rubygems'
2
+ require File.expand_path('../../lib/faye/websocket', __FILE__)
3
+ require 'eventmachine'
4
+
5
+ port = ARGV[0] || 7000
6
+ secure = ARGV[1] == 'ssl'
7
+
8
+ EM.run {
9
+ scheme = secure ? 'wss' : 'ws'
10
+ url = "#{scheme}://localhost:#{port}/"
11
+ socket = Faye::WebSocket::Client.new(url)
12
+
13
+ puts "Connecting to #{socket.url}"
14
+
15
+ socket.onopen = lambda do |event|
16
+ p [:open]
17
+ socket.send("Hello, WebSocket!")
18
+ end
19
+
20
+ socket.onmessage = lambda do |event|
21
+ p [:message, event.data]
22
+ # socket.close 1002, 'Going away'
23
+ end
24
+
25
+ socket.onclose = lambda do |event|
26
+ p [:close, event.code, event.reason]
27
+ EM.stop
28
+ end
29
+ }
30
+
@@ -0,0 +1,17 @@
1
+ # Run using your favourite async server:
2
+ #
3
+ # thin start -R examples/config.ru -p 7000
4
+ # rainbows -c spec/rainbows.conf -E production examples/config.ru -p 7000
5
+ #
6
+ # If you run using one of these commands, the webserver is loaded before this
7
+ # file, so Faye::WebSocket can figure out which adapter to load. If instead you
8
+ # run using `rackup`, you need the `load_adapter` line below.
9
+ #
10
+ # rackup -E production -s thin examples/config.ru -p 7000
11
+
12
+ require 'rubygems'
13
+ require File.expand_path('../app', __FILE__)
14
+ # Faye::WebSocket.load_adapter('thin')
15
+
16
+ run App
17
+
@@ -0,0 +1,21 @@
1
+ defaults
2
+ mode http
3
+ timeout client 5s
4
+ timeout connect 5s
5
+ timeout server 5s
6
+
7
+ frontend all 0.0.0.0:3000
8
+ mode http
9
+ timeout client 120s
10
+
11
+ option forwardfor
12
+ option http-server-close
13
+ option http-pretend-keepalive
14
+
15
+ default_backend sockets
16
+
17
+ backend sockets
18
+ balance uri depth 2
19
+ timeout server 120s
20
+ server socket1 127.0.0.1:7000
21
+
@@ -0,0 +1,44 @@
1
+ require 'rubygems'
2
+ require 'rack/content_length'
3
+ require 'rack/chunked'
4
+
5
+ port = ARGV[0] || 7000
6
+ secure = ARGV[1] == 'ssl'
7
+ engine = ARGV[2] || 'thin'
8
+ spec = File.expand_path('../../spec', __FILE__)
9
+
10
+ require File.expand_path('../app', __FILE__)
11
+ Faye::WebSocket.load_adapter(engine)
12
+
13
+ case engine
14
+
15
+ when 'goliath'
16
+ class WebSocketServer < Goliath::API
17
+ def response(env)
18
+ App.call(env)
19
+ end
20
+ end
21
+
22
+ when 'rainbows'
23
+ rackup = Unicorn::Configurator::RACKUP
24
+ rackup[:port] = port
25
+ rackup[:set_listener] = true
26
+ options = rackup[:options]
27
+ options[:config_file] = spec + '/rainbows.conf'
28
+ Rainbows::HttpServer.new(App, options).start.join
29
+
30
+ when 'thin'
31
+ EM.run {
32
+ thin = Rack::Handler.get('thin')
33
+ thin.run(App, :Port => port) do |server|
34
+ if secure
35
+ server.ssl_options = {
36
+ :private_key_file => spec + '/server.key',
37
+ :cert_chain_file => spec + '/server.crt'
38
+ }
39
+ server.ssl = true
40
+ end
41
+ end
42
+ }
43
+ end
44
+
@@ -0,0 +1,39 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <meta http-equiv="Content-type" content="text/html; charset=utf-8">
5
+ <title>EventSource test</title>
6
+ </head>
7
+ <body>
8
+
9
+ <h1>EventSource test</h1>
10
+ <ul></ul>
11
+
12
+ <script type="text/javascript">
13
+ var logger = document.getElementsByTagName('ul')[0],
14
+ socket = new EventSource('/');
15
+
16
+ var log = function(text) {
17
+ logger.innerHTML += '<li>' + text + '</li>';
18
+ };
19
+
20
+ socket.onopen = function() {
21
+ log('OPEN');
22
+ };
23
+
24
+ socket.onmessage = function(event) {
25
+ log('MESSAGE: ' + event.data);
26
+ };
27
+
28
+ socket.addEventListener('update', function(event) {
29
+ log('UPDATE(' + event.lastEventId + '): ' + event.data);
30
+ });
31
+
32
+ socket.onerror = function(event) {
33
+ log('ERROR: ' + event.message);
34
+ };
35
+ </script>
36
+
37
+ </body>
38
+ </html>
39
+
@@ -0,0 +1,44 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <meta http-equiv="Content-type" content="text/html; charset=utf-8">
5
+ <title>WebSocket test</title>
6
+ </head>
7
+ <body>
8
+
9
+ <h1>WebSocket test</h1>
10
+ <ul></ul>
11
+
12
+ <script type="text/javascript">
13
+ var logger = document.getElementsByTagName('ul')[0],
14
+ Socket = window.MozWebSocket || window.WebSocket,
15
+ protos = ['foo', 'bar', 'xmpp'],
16
+ socket = new Socket('ws://' + location.hostname + ':' + location.port + '/', protos),
17
+ index = 0;
18
+
19
+ var log = function(text) {
20
+ logger.innerHTML += '<li>' + text + '</li>';
21
+ };
22
+
23
+ socket.addEventListener('open', function() {
24
+ log('OPEN: ' + socket.protocol);
25
+ socket.send('Hello, world');
26
+ });
27
+
28
+ socket.onerror = function(event) {
29
+ log('ERROR: ' + event.message);
30
+ };
31
+
32
+ socket.onmessage = function(event) {
33
+ log('MESSAGE: ' + event.data);
34
+ setTimeout(function() { socket.send(++index + ' ' + event.data) }, 2000);
35
+ };
36
+
37
+ socket.onclose = function(event) {
38
+ log('CLOSE: ' + event.code + ', ' + event.reason);
39
+ };
40
+ </script>
41
+
42
+ </body>
43
+ </html>
44
+
@@ -0,0 +1,61 @@
1
+ package com.jcoglan.faye;
2
+
3
+ import java.lang.Long;
4
+ import java.io.IOException;
5
+
6
+ import org.jruby.Ruby;
7
+ import org.jruby.RubyArray;
8
+ import org.jruby.RubyClass;
9
+ import org.jruby.RubyFixnum;
10
+ import org.jruby.RubyModule;
11
+ import org.jruby.RubyObject;
12
+ import org.jruby.anno.JRubyMethod;
13
+ import org.jruby.runtime.ObjectAllocator;
14
+ import org.jruby.runtime.ThreadContext;
15
+ import org.jruby.runtime.builtin.IRubyObject;
16
+ import org.jruby.runtime.load.BasicLibraryService;
17
+
18
+ public class FayeWebsocketMaskService implements BasicLibraryService {
19
+ private Ruby runtime;
20
+
21
+ public boolean basicLoad(Ruby runtime) throws IOException {
22
+ this.runtime = runtime;
23
+ RubyModule faye = runtime.defineModule("Faye");
24
+
25
+ RubyClass webSocketMask = faye.defineClassUnder("WebSocketMask", runtime.getObject(), new ObjectAllocator() {
26
+ public IRubyObject allocate(Ruby runtime, RubyClass rubyClass) {
27
+ return new WebsocketMask(runtime, rubyClass);
28
+ }
29
+ });
30
+
31
+ webSocketMask.defineAnnotatedMethods(WebsocketMask.class);
32
+ return true;
33
+ }
34
+
35
+ public class WebsocketMask extends RubyObject {
36
+ public WebsocketMask(final Ruby runtime, RubyClass rubyClass) {
37
+ super(runtime, rubyClass);
38
+ }
39
+
40
+ @JRubyMethod
41
+ public IRubyObject mask(ThreadContext context, IRubyObject payload, IRubyObject mask) {
42
+ int n = ((RubyArray)payload).getLength(), i;
43
+ long p, m;
44
+ RubyArray unmasked = RubyArray.newArray(runtime, n);
45
+
46
+ long[] maskArray = {
47
+ (Long)((RubyArray)mask).get(0),
48
+ (Long)((RubyArray)mask).get(1),
49
+ (Long)((RubyArray)mask).get(2),
50
+ (Long)((RubyArray)mask).get(3)
51
+ };
52
+
53
+ for (i = 0; i < n; i++) {
54
+ p = (Long)((RubyArray)payload).get(i);
55
+ m = maskArray[i % 4];
56
+ unmasked.set(i, p ^ m);
57
+ }
58
+ return unmasked;
59
+ }
60
+ }
61
+ }
@@ -0,0 +1,5 @@
1
+ require 'mkmf'
2
+ extension_name = 'faye_websocket_mask'
3
+ dir_config(extension_name)
4
+ create_makefile(extension_name)
5
+
@@ -0,0 +1,33 @@
1
+ #include <ruby.h>
2
+
3
+ VALUE Faye = Qnil;
4
+ VALUE FayeWebSocketMask = Qnil;
5
+
6
+ void Init_faye_websocket_mask();
7
+ VALUE method_faye_websocket_mask(VALUE self, VALUE payload, VALUE mask);
8
+
9
+ void Init_faye_websocket_mask() {
10
+ Faye = rb_define_module("Faye");
11
+ FayeWebSocketMask = rb_define_module_under(Faye, "WebSocketMask");
12
+ rb_define_singleton_method(FayeWebSocketMask, "mask", method_faye_websocket_mask, 2);
13
+ }
14
+
15
+ VALUE method_faye_websocket_mask(VALUE self, VALUE payload, VALUE mask) {
16
+ int n = RARRAY_LEN(payload), i, p, m;
17
+ VALUE unmasked = rb_ary_new2(n);
18
+
19
+ int mask_array[] = {
20
+ NUM2INT(rb_ary_entry(mask, 0)),
21
+ NUM2INT(rb_ary_entry(mask, 1)),
22
+ NUM2INT(rb_ary_entry(mask, 2)),
23
+ NUM2INT(rb_ary_entry(mask, 3))
24
+ };
25
+
26
+ for (i = 0; i < n; i++) {
27
+ p = NUM2INT(rb_ary_entry(payload, i));
28
+ m = mask_array[i % 4];
29
+ rb_ary_store(unmasked, i, INT2NUM(p ^ m));
30
+ }
31
+ return unmasked;
32
+ }
33
+
@@ -0,0 +1,47 @@
1
+ class Goliath::Connection
2
+ attr_accessor :socket_stream
3
+ alias :goliath_receive_data :receive_data
4
+
5
+ def receive_data(data)
6
+ if @serving == :websocket
7
+ socket_stream.receive(data) if socket_stream
8
+ else
9
+ goliath_receive_data(data)
10
+ socket_stream.receive(@parser.upgrade_data) if socket_stream
11
+ @serving = :websocket if @api.websocket?
12
+ end
13
+ end
14
+
15
+ def unbind
16
+ super
17
+ ensure
18
+ socket_stream.fail if socket_stream
19
+ end
20
+ end
21
+
22
+ class Goliath::API
23
+ include Faye::WebSocket::Adapter
24
+ end
25
+
26
+ class Goliath::Request
27
+ alias :goliath_process :process
28
+
29
+ def process
30
+ env['em.connection'] = conn
31
+ goliath_process
32
+ end
33
+ end
34
+
35
+ class Goliath::Response
36
+ alias :goliath_head :head
37
+ alias :goliath_headers_output :headers_output
38
+
39
+ def head
40
+ (status == 101) ? '' : goliath_head
41
+ end
42
+
43
+ def headers_output
44
+ (status == 101) ? '' : goliath_headers_output
45
+ end
46
+ end
47
+
@@ -0,0 +1,32 @@
1
+ # WebSocket extensions for Rainbows
2
+ # Based on code from the Cramp project
3
+ # http://github.com/lifo/cramp
4
+
5
+ # Copyright (c) 2009-2011 Pratik Naik
6
+ #
7
+ # Permission is hereby granted, free of charge, to any person obtaining
8
+ # a copy of this software and associated documentation files (the
9
+ # "Software"), to deal in the Software without restriction, including
10
+ # without limitation the rights to use, copy, modify, merge, publish,
11
+ # distribute, sublicense, and/or sell copies of the Software, and to
12
+ # permit persons to whom the Software is furnished to do so, subject to
13
+ # the following conditions:
14
+ #
15
+ # The above copyright notice and this permission notice shall be
16
+ # included in all copies or substantial portions of the Software.
17
+ #
18
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25
+
26
+ module Faye
27
+ class WebSocket
28
+ autoload :RainbowsClient, File.expand_path('../rainbows_client', __FILE__)
29
+ end
30
+ end
31
+
32
+ Rainbows::O[:em_client_class] = "Faye::WebSocket::RainbowsClient"
@@ -0,0 +1,70 @@
1
+ # WebSocket extensions for Rainbows
2
+ # Based on code from the Cramp project
3
+ # http://github.com/lifo/cramp
4
+
5
+ # Copyright (c) 2009-2011 Pratik Naik
6
+ #
7
+ # Permission is hereby granted, free of charge, to any person obtaining
8
+ # a copy of this software and associated documentation files (the
9
+ # "Software"), to deal in the Software without restriction, including
10
+ # without limitation the rights to use, copy, modify, merge, publish,
11
+ # distribute, sublicense, and/or sell copies of the Software, and to
12
+ # permit persons to whom the Software is furnished to do so, subject to
13
+ # the following conditions:
14
+ #
15
+ # The above copyright notice and this permission notice shall be
16
+ # included in all copies or substantial portions of the Software.
17
+ #
18
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25
+
26
+ module Faye
27
+ class WebSocket
28
+
29
+ class RainbowsClient < Rainbows::EventMachine::Client
30
+ include Faye::WebSocket::Adapter
31
+ attr_accessor :socket_stream
32
+
33
+ def receive_data(data)
34
+ return super unless @state == :websocket
35
+ socket_stream.receive(data) if socket_stream
36
+ end
37
+
38
+ def app_call(*args)
39
+ @env['em.connection'] = self
40
+ if args.first == NULL_IO and @hp.content_length == 0 and websocket?
41
+ prepare_request_body
42
+ else
43
+ super
44
+ end
45
+ end
46
+
47
+ def on_read(data)
48
+ if @state == :body and websocket? and @hp.body_eof?
49
+ @state = :websocket
50
+ @input.rewind
51
+ app_call StringIO.new(@buf)
52
+ else
53
+ super
54
+ end
55
+ end
56
+
57
+ def unbind
58
+ super
59
+ ensure
60
+ socket_stream.fail if socket_stream
61
+ end
62
+
63
+ def write_headers(*args)
64
+ super unless async_connection?
65
+ end
66
+ end
67
+
68
+ end
69
+ end
70
+