sinatra-websocket 0.1.0

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