_bushido-faye-websocket 0.4.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.
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
+