sinatra-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.
@@ -0,0 +1,90 @@
1
+ # SinatraWebsocket
2
+
3
+ Makes it easy to upgrade any request to a websocket connection.
4
+
5
+ SinatraWebsocket is a fork of [Skinny](https://github.com/sj26/skinny) merged with [Rack WebSocket](https://github.com/igrigorik/em-websocket). It provides helpers methods to detect if an request is a WebSocket request and defer to an EM::WebSocket::Connection.
6
+
7
+
8
+ ## Put this in your pipe ...
9
+
10
+ ```ruby
11
+
12
+ require 'sinatra'
13
+ require 'sinatra-websocket'
14
+
15
+ set :server, 'thin'
16
+ set :sockets, []
17
+
18
+ get '/' do
19
+ if !request.websocket?
20
+ erb :index
21
+ else
22
+ request.websocket do |ws|
23
+ ws.onopen do
24
+ ws.send("Hello World!")
25
+ settings.sockets << ws
26
+ end
27
+ ws.onmessage do |msg|
28
+ EM.next_tick { settings.sockets.each{|s| s.send(msg) } }
29
+ end
30
+ ws.onclose do
31
+ warn("wetbsocket closed")
32
+ settings.sockets.delete(ws)
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ __END__
39
+ @@ index
40
+ <html>
41
+ <body>
42
+ <h1>Simple Echo & Chat Server</h1>
43
+ <form id="form">
44
+ <input type="text" id="input" value="send a message"></input>
45
+ </form>
46
+ <div id="msgs"></div>
47
+ </body>
48
+
49
+ <script type="text/javascript">
50
+ window.onload = function(){
51
+ (function(){
52
+ var show = function(el){
53
+ return function(msg){ el.innerHTML = msg + '<br />' + el.innerHTML; }
54
+ }(document.getElementById('msgs'));
55
+
56
+ var ws = new WebSocket('ws://' + window.location.host + window.location.pathname);
57
+ ws.onopen = function() { show('websocket opened'); };
58
+ ws.onclose = function() { show('websocket closed'); }
59
+ ws.onmessage = function(m) { show('websocket message: ' + m.data); };
60
+
61
+ var sender = function(f){
62
+ var input = document.getElementById('input');
63
+ input.onclick = function(){ input.value = "" };
64
+ f.onsubmit = function(){
65
+ ws.send(input.value);
66
+ input.value = "send a message";
67
+ return false;
68
+ }
69
+ }(document.getElementById('form'));
70
+ })();
71
+ }
72
+ </script>
73
+ </html>
74
+
75
+ ```
76
+
77
+ ## And Smoke It
78
+
79
+ ```
80
+ ruby echo.rb
81
+ ```
82
+
83
+
84
+ ## Copyright
85
+
86
+ Copyright (c) 2012 Caleb Crane.
87
+
88
+ Portions of this software are Copyright (c) Bernard Potocki <bernard.potocki@imanel.org> and Samuel Cochran.
89
+
90
+ See License.txt for more details.
@@ -0,0 +1,115 @@
1
+ require 'thin'
2
+ require 'em-websocket'
3
+ require 'sinatra-websocket/ext/thin/connection'
4
+ require 'sinatra-websocket/ext/sinatra/request'
5
+
6
+ module SinatraWebsocket
7
+ class Connection < ::EventMachine::WebSocket::Connection
8
+ class << self
9
+ def from_env(env, options = {})
10
+ socket = env[Thin::Request::ASYNC_CALLBACK].receiver
11
+ request = request_from_env(env)
12
+ connection = Connection.new(env, socket, :debug => options[:debug])
13
+ yield(connection) if block_given?
14
+ connection.dispatch(request) ? async_response : failure_response
15
+ end
16
+
17
+ #######
18
+ # Taken from WebSocket Rack
19
+ # https://github.com/imanel/websocket-rack
20
+ #######
21
+
22
+ # Parse Rack env to em-websocket-compatible format
23
+ # this probably should be moved to Base in future
24
+ def request_from_env(env)
25
+ request = {}
26
+ request['path'] = env['REQUEST_URI'].to_s
27
+ request['method'] = env['REQUEST_METHOD']
28
+ request['query'] = env['QUERY_STRING'].to_s
29
+ request['Body'] = env['rack.input'].read
30
+
31
+ env.each do |key, value|
32
+ if key.match(/HTTP_(.+)/)
33
+ request[$1.downcase.gsub('_','-')] ||= value
34
+ end
35
+ end
36
+ request
37
+ end
38
+
39
+ # Standard async response
40
+ def async_response
41
+ [-1, {}, []]
42
+ end
43
+
44
+ # Standard 400 response
45
+ def failure_response
46
+ [ 400, {'Content-Type' => 'text/plain'}, [ 'Bad request' ] ]
47
+ end
48
+ end # class << self
49
+
50
+
51
+ #########################
52
+ ### EventMachine part ###
53
+ #########################
54
+
55
+ # Overwrite new from EventMachine
56
+ # we need to skip standard procedure called
57
+ # when socket is created - this is just a stub
58
+ def self.new(*args)
59
+ instance = allocate
60
+ instance.__send__(:initialize, *args)
61
+ instance
62
+ end
63
+
64
+ # Overwrite send_data from EventMachine
65
+ # delegate send_data to rack server
66
+ def send_data(*args)
67
+ EM.next_tick do
68
+ @socket.send_data(*args)
69
+ end
70
+ end
71
+
72
+ # Overwrite close_connection from EventMachine
73
+ # delegate close_connection to rack server
74
+ def close_connection(*args)
75
+ EM.next_tick do
76
+ @socket.close_connection(*args)
77
+ end
78
+ end
79
+
80
+ #########################
81
+ ### EM-WebSocket part ###
82
+ #########################
83
+
84
+ # Overwrite initialize from em-websocket
85
+ # set all standard options and disable
86
+ # EM connection inactivity timeout
87
+ def initialize(app, socket, options = {})
88
+ @app = app
89
+ @socket = socket
90
+ @options = options
91
+ @debug = options[:debug] || false
92
+ @ssl = socket.backend.respond_to?(:ssl?) && socket.backend.ssl?
93
+
94
+ socket.websocket = self
95
+ socket.comm_inactivity_timeout = 0
96
+
97
+ debug [:initialize]
98
+ end
99
+
100
+ # Overwrite dispath from em-websocket
101
+ # we already have request headers parsed so
102
+ # we can skip it and call build_with_request
103
+ def dispatch(data)
104
+ return false if data.nil?
105
+ debug [:inbound_headers, data]
106
+ @handler = EventMachine::WebSocket::HandlerFactory.build_with_request(self, data, data['Body'], @ssl, @debug)
107
+ unless @handler
108
+ # The whole header has not been received yet.
109
+ return false
110
+ end
111
+ @handler.run
112
+ return true
113
+ end
114
+ end
115
+ end # module::SinatraWebSocket
@@ -0,0 +1,23 @@
1
+ module SinatraWebsocket
2
+ module Ext
3
+ module Sinatra
4
+ module Request
5
+
6
+ # Taken from skinny https://github.com/sj26/skinny and updated to support Firefox
7
+ def websocket?
8
+ env['HTTP_CONNECTION'].split(',').map(&:strip).map(&:downcase).include?('upgrade') &&
9
+ env['HTTP_UPGRADE'].downcase == 'websocket'
10
+ end
11
+
12
+ # Taken from skinny https://github.com/sj26/skinny
13
+ def websocket(options={}, &blk)
14
+ env['skinny.websocket'] ||= begin
15
+ raise RuntimeError, "Not a WebSocket request" unless websocket?
16
+ SinatraWebsocket::Connection.from_env(env, options, &blk)
17
+ end
18
+ end
19
+ end
20
+ end # module::Sinatra
21
+ end # module::Ext
22
+ end # module::SinatraWebsocket
23
+ defined?(Sinatra) && Sinatra::Request.send(:include, SinatraWebsocket::Ext::Sinatra::Request)
@@ -0,0 +1,76 @@
1
+ module SinatraWebsocket
2
+ module Ext
3
+ module Thin
4
+ module Connection
5
+ def self.included(base)
6
+ base.class_eval do
7
+ alias :receive_data_without_websocket :receive_data
8
+ alias :receive_data :receive_data_with_websocket
9
+
10
+ alias :unbind_without_websocket :unbind
11
+ alias :unbind :unbind_with_websocket
12
+
13
+ alias :receive_data_without_flash_policy_file :receive_data
14
+ alias :receive_data :receive_data_with_flash_policy_file
15
+
16
+ alias :pre_process_without_websocket :pre_process
17
+ alias :pre_process :pre_process_with_websocket
18
+ end
19
+ end
20
+
21
+ attr_accessor :websocket
22
+
23
+ # Set 'async.connection' Rack env
24
+ def pre_process_with_websocket
25
+ @request.env['async.connection'] = self
26
+ pre_process_without_websocket
27
+ end
28
+
29
+ # Is this connection WebSocket?
30
+ def websocket?
31
+ !self.websocket.nil?
32
+ end
33
+
34
+ # Skip default receive_data if this is
35
+ # WebSocket connection
36
+ def receive_data_with_websocket(data)
37
+ if self.websocket?
38
+ self.websocket.receive_data(data)
39
+ else
40
+ receive_data_without_websocket(data)
41
+ end
42
+ end
43
+
44
+ # Skip standard unbind it this is
45
+ # WebSocket connection
46
+ def unbind_with_websocket
47
+ if self.websocket?
48
+ self.websocket.unbind
49
+ else
50
+ unbind_without_websocket
51
+ end
52
+ end
53
+
54
+ # Send flash policy file if requested
55
+ def receive_data_with_flash_policy_file(data)
56
+ # thin require data to be proper http request - in it's not
57
+ # then @request.parse raises exception and data isn't parsed
58
+ # by futher methods. Here we only check if it is flash
59
+ # policy file request ("<policy-file-request/>\000") and
60
+ # if so then flash policy file is returned. if not then
61
+ # rest of request is handled.
62
+ if (data == "<policy-file-request/>\000")
63
+ file = '<?xml version="1.0"?><cross-domain-policy><allow-access-from domain="*" to-ports="*"/></cross-domain-policy>'
64
+ # ignore errors - we will close this anyway
65
+ send_data(file) rescue nil
66
+ close_connection_after_writing
67
+ else
68
+ receive_data_without_flash_policy_file(data)
69
+ end
70
+ end
71
+
72
+ end # module::Connection
73
+ end # module::Thin
74
+ end # module::Ext
75
+ end # module::SinatraWebsocket
76
+ defined?(Thin) && Thin::Connection.send(:include, SinatraWebsocket::Ext::Thin::Connection)
@@ -0,0 +1,4 @@
1
+ module SinatraWebsocket
2
+ VERSION = '0.1.0'
3
+ end
4
+
metadata ADDED
@@ -0,0 +1,97 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sinatra-websocket
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Caleb Crane
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-04-25 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: eventmachine
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: thin
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: 1.3.1
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: 1.3.1
46
+ - !ruby/object:Gem::Dependency
47
+ name: em-websocket
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: 0.3.6
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: 0.3.6
62
+ description: Makes it easy to upgrade any request to a websocket connection in Sinatra
63
+ email: sinatra-websocket@simulacre.org
64
+ executables: []
65
+ extensions: []
66
+ extra_rdoc_files: []
67
+ files:
68
+ - lib/sinatra-websocket/ext/sinatra/request.rb
69
+ - lib/sinatra-websocket/ext/thin/connection.rb
70
+ - lib/sinatra-websocket/version.rb
71
+ - lib/sinatra-websocket.rb
72
+ - README.md
73
+ homepage: http://github.com/simulacre/sinatra-websocket
74
+ licenses: []
75
+ post_install_message:
76
+ rdoc_options: []
77
+ require_paths:
78
+ - lib
79
+ required_ruby_version: !ruby/object:Gem::Requirement
80
+ none: false
81
+ requirements:
82
+ - - ! '>='
83
+ - !ruby/object:Gem::Version
84
+ version: '0'
85
+ required_rubygems_version: !ruby/object:Gem::Requirement
86
+ none: false
87
+ requirements:
88
+ - - ! '>='
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ requirements: []
92
+ rubyforge_project:
93
+ rubygems_version: 1.8.21
94
+ signing_key:
95
+ specification_version: 3
96
+ summary: Simple, upgradable WebSockets for Sinatra.
97
+ test_files: []