sloth-reel 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,132 @@
1
+ require 'forwardable'
2
+ require 'websocket'
3
+
4
+ module Reel
5
+ class WebSocket
6
+ extend Forwardable
7
+ include ConnectionMixin
8
+ include RequestMixin
9
+
10
+ attr_reader :socket, :url
11
+ def_delegators :@socket, :addr, :peeraddr
12
+
13
+ READ_SIZE = 0x4000
14
+ @@sockets = []
15
+ @@conns = {}
16
+ @@actions = Hash.new { |h,k| h[k] = {} }
17
+ @@thread = nil
18
+
19
+ def initialize(info, connection)
20
+ @opened = false
21
+ @socket = connection.hijack_socket
22
+ @request_info = info
23
+ @url = @request_info.url
24
+
25
+ @handshake = ::WebSocket::Handshake::Server.new
26
+ @frame = ::WebSocket::Frame::Incoming::Server.new(:version => @handshake.version)
27
+
28
+ line = "#{@request_info.method} / HTTP/#{@request_info.version}\r\n"
29
+ @handshake << line
30
+ @request_info.headers.each do |k, v|
31
+ line = "#{k}: #{v}\r\n"
32
+ @handshake << line
33
+ end
34
+ @handshake << "\r\n"
35
+
36
+ if not @handshake.finished? or not @handshake.valid?
37
+ @socket.close
38
+ return
39
+ end
40
+
41
+ lines = @handshake.to_s
42
+ @socket.write lines
43
+
44
+ @@sockets.push( @socket )
45
+ @@conns[@socket] = self
46
+
47
+ @@thread ||= Thread.start do
48
+ loop do
49
+ readables, = ::IO.select( @@sockets, nil, nil, 1 )
50
+ while sock = readables&.shift
51
+ if sock.eof?
52
+ @@conns[sock]&.close
53
+ break
54
+ else
55
+ payload = sock.readpartial( READ_SIZE ) rescue nil
56
+ @@conns[sock]&.parse( payload ) if payload
57
+ end
58
+ end
59
+ end
60
+ end
61
+
62
+ end
63
+
64
+ def parse( payload )
65
+ @frame << payload
66
+ messages = []
67
+ while frame = @frame.next
68
+ if (frame.type == :close)
69
+ close
70
+ break
71
+ else
72
+ messages.push frame.to_s
73
+ end
74
+ end
75
+
76
+ conns = @@conns.values.map do |conn|
77
+ conn if conn.url == @url
78
+ end.compact
79
+ messages.each do |mesg|
80
+ @@actions[@url][:onmessage]&.call( mesg, self, conns ) rescue nil
81
+ end
82
+ end
83
+
84
+ def send( mesg, type: :text )
85
+ frame = ::WebSocket::Frame::Outgoing::Server.new( :version => @handshake.version, :data => mesg, :type => type )
86
+ begin
87
+ @socket.write frame.to_s
88
+ @socket.flush
89
+ rescue
90
+ close
91
+ raise Reel::SocketError
92
+ end
93
+ end
94
+ alias write send
95
+ alias :<< send
96
+
97
+ def close
98
+ return if @socket.closed?
99
+ @@actions[@url][:onclose]&.call( self ) rescue nil
100
+ @@conns.delete( @socket )
101
+ @@sockets.delete( @socket )
102
+ @@sockets.compact!
103
+ @socket.close rescue nil
104
+ end
105
+
106
+ def closed?
107
+ @socket.closed?
108
+ end
109
+
110
+ def connections
111
+ @@conns.values
112
+ end
113
+
114
+ def on_open( &block )
115
+ @@actions[@url][:onopen] = block
116
+ if not @opened
117
+ @opened = true
118
+ block.call( self ) rescue nil
119
+ end
120
+ end
121
+
122
+ def on_message( &block )
123
+ @@actions[@url][:onmessage] = block
124
+ end
125
+
126
+ def on_close( &block )
127
+ @@actions[@url][:onclose] = block
128
+ end
129
+
130
+ end
131
+ end
132
+
@@ -0,0 +1,13 @@
1
+ require "sloth/reel/version"
2
+ require "celluloid/current"
3
+ require "celluloid/autostart"
4
+ require "celluloid/fsm"
5
+ require "reel/rack"
6
+ require "sinatra/base"
7
+ require "sloth/reel/sinatra"
8
+
9
+ module Sloth
10
+ module Reel
11
+ class Error < StandardError; end
12
+ end
13
+ end
@@ -0,0 +1,18 @@
1
+ require "sinatra/base"
2
+
3
+ module Sloth
4
+ module Reel
5
+ module Sinatra
6
+ def websocket?
7
+ !!env['websocket']
8
+ end
9
+
10
+ def websocket
11
+ env['websocket']
12
+ end
13
+ end
14
+ end
15
+ end
16
+
17
+ defined?( ::Sinatra ) && Sinatra::Request.send( :include, ::Sloth::Reel::Sinatra )
18
+
@@ -0,0 +1,5 @@
1
+ module Sloth
2
+ module Reel
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
@@ -0,0 +1,74 @@
1
+ require 'sloth/reel'
2
+
3
+ class WebApp
4
+ def call( env )
5
+ r = nil
6
+ case env['REQUEST_METHOD']
7
+ when 'GET'
8
+ case env['PATH_INFO']
9
+ when '/'
10
+ get_index
11
+ when '/heavy'
12
+ get_heavy
13
+ end
14
+
15
+ when 'POST'
16
+ case env['PATH_INFO']
17
+ when '/'
18
+ post_index
19
+ end
20
+
21
+ else
22
+ not_found
23
+
24
+ end
25
+ end
26
+
27
+ def not_found
28
+ [
29
+ 404,
30
+ {'Content-Type' => 'text/html'},
31
+ ["<html><body><h1> 404: Not found: #{env['REQUEST_METHOD']} #{env['PATH_INFO']} </h1> </body></html>"]
32
+ ]
33
+ end
34
+
35
+ def get_index
36
+ [
37
+ 200,
38
+ {'Content-Type' => 'text/html'},
39
+ ['<html><body><h1> GET </h1><a href="/"> Hello (GET) </a> <form method="POST"><input type="submit" value="Howdy (POST)" /></form></body></html>']
40
+ ]
41
+ end
42
+
43
+ def post_index
44
+ [
45
+ 200,
46
+ {'Content-Type' => 'text/html'},
47
+ ['<html><body><h1> POST </h1><a href="/"> Hello (GET) </a> <form method="POST"><input type="submit" value="Howdy (POST)" /></form></body></html>']
48
+ ]
49
+ end
50
+
51
+ def get_heavy
52
+ sec = 10
53
+ tm1 = Time.now.to_s
54
+ Kernel.sleep sec
55
+ tm2 = Time.now.to_s
56
+
57
+ [
58
+ 200,
59
+ {'Content-Type' => 'text/html'},
60
+ ["<html><body><p> #{sec}: interval <br/> #{tm1}: sleep <br/> #{tm2}: wakeup </p></body></html>"]
61
+ ]
62
+ end
63
+ end
64
+
65
+ ENV['RACK_ENV'] ||= 'production'
66
+ options = {Host: "0.0.0.0", Port: 3000, max_connection: 16}
67
+ Reel::Rack::Server.new( WebApp.new, options )
68
+
69
+ Signal.trap(:INT) do
70
+ exit
71
+ end
72
+
73
+ sleep
74
+
@@ -0,0 +1,84 @@
1
+ require 'sloth/reel'
2
+
3
+ class WebServer < Reel::Server::HTTP
4
+ include Celluloid::Internals::Logger
5
+
6
+ def initialize( options )
7
+ host = options[:Host] || "0.0.0.0"
8
+ port = options[:Port] || 3000
9
+ opts = {}
10
+ opts[:max_connection] = options[:max_connection] || 16
11
+
12
+ info "Reel::Server starting on #{host}:#{port}"
13
+ super( host, port, opts, &method(:on_connection) )
14
+ end
15
+
16
+ def on_connection( connection )
17
+ while request = connection.request
18
+ route_request connection, request
19
+ end
20
+ end
21
+
22
+ def route_request( connection, request )
23
+ case request.method
24
+ when "GET"
25
+ case request.url
26
+ when "/"
27
+ get_index( connection )
28
+ when "/heavy"
29
+ get_heavy( connection )
30
+ end
31
+
32
+ when "POST"
33
+ case request.url
34
+ when "/"
35
+ post_index( connection )
36
+ end
37
+
38
+ else
39
+ not_found( connection )
40
+
41
+ end
42
+ end
43
+
44
+ def not_found( connection )
45
+ info "404 Not Found: #{request.method} #{request.path}"
46
+ connection.respond :not_found, "Not found"
47
+ end
48
+
49
+ def get_index( connection )
50
+ info "200 OK: GET /"
51
+ connection.respond :ok, <<-HTML
52
+ <html><body><h1> GET </h1><a href="/"> Hello (GET) </a> <form method="POST"><input type="submit" value="Howdy (POST)" /></form></body></html>
53
+ HTML
54
+ end
55
+
56
+ def post_index( connection )
57
+ info "200 OK: POST /"
58
+ connection.respond :ok, <<-HTML
59
+ <html><body><h1> POST </h1><a href="/"> Hello (GET) </a> <form method="POST"><input type="submit" value="Howdy (POST)" /></form></body></html>
60
+ HTML
61
+ end
62
+
63
+ def get_heavy( connection )
64
+ info "200 OK: POST /"
65
+
66
+ sec = 10
67
+ tm1 = Time.now.to_s
68
+ Kernel.sleep sec
69
+ tm2 = Time.now.to_s
70
+
71
+ connection.respond :ok, <<-HTML
72
+ <html><body><p> #{sec}: interval <br/> #{tm1}: sleep <br/> #{tm2}: wakeup </p></body></html>
73
+ HTML
74
+ end
75
+ end
76
+
77
+ options = {Host: "0.0.0.0", Port: 3000, max_connection: 16}
78
+ WebServer.new( options )
79
+
80
+ Signal.trap(:INT) do
81
+ exit
82
+ end
83
+
84
+ sleep
@@ -0,0 +1,105 @@
1
+ require 'sloth/reel'
2
+
3
+ class WebServer < Reel::Server::HTTP
4
+ include Celluloid::Internals::Logger
5
+
6
+ def initialize( options )
7
+ host = options[:Host] || "0.0.0.0"
8
+ port = options[:Port] || 3000
9
+ opts = {}
10
+ opts[:max_connection] = options[:max_connection] || 16
11
+
12
+ info "Reel::Server starting on #{host}:#{port}"
13
+ super( host, port, opts, &method(:on_connection) )
14
+ end
15
+
16
+ def on_connection(connection)
17
+ while request = connection.request
18
+ if request.websocket?
19
+ connection.detach
20
+ route_websocket( request )
21
+ else
22
+ route_request( connection, request )
23
+ end
24
+ end
25
+ end
26
+
27
+ def route_websocket( request )
28
+ case request.url
29
+ when "/chat"
30
+ route_chat( request )
31
+ end
32
+ end
33
+
34
+ def route_chat(request)
35
+ if request.websocket?
36
+ ws = request.websocket
37
+ ws << Time.now.to_s
38
+ ws.on_message do |mesg, sender, conns|
39
+ conns.each do |conn|
40
+ conn << mesg
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ def route_request(connection, request)
47
+ case request.url
48
+ when "/"
49
+ render_index(connection)
50
+ else
51
+ info "404 Not Found: #{request.path}"
52
+ connection.respond :not_found, "Not found"
53
+ end
54
+ end
55
+
56
+ def render_index(connection)
57
+ info "200 OK: /"
58
+ connection.respond :ok, <<-HTML
59
+ <!doctype html>
60
+ <html>
61
+ <body>
62
+ <h1>WebSocket Chat</h1>
63
+ <form id="form"> <input type="text" id="input" value=""></input> </form>
64
+ <div id="msgs"></div>
65
+ </body>
66
+
67
+ <script type="text/javascript">
68
+ window.onload = function(){
69
+ (function(){
70
+ var show = function(el){
71
+ return function(str){ el.innerHTML = str + '<br />' + el.innerHTML }
72
+ }(document.getElementById('msgs'))
73
+
74
+ ws = new WebSocket('ws://' + window.location.host + '/chat')
75
+ ws.onopen = function() { show('[opened]') }
76
+ ws.onclose = function() { show('[closed]') }
77
+ ws.onmessage = function(mesg) { show(mesg.data) }
78
+
79
+ var sender = function(fm) {
80
+ var input = document.getElementById('input')
81
+ input.onclick = function(){
82
+ input.value = ""
83
+ }
84
+ fm.onsubmit = function(){
85
+ ws.send( input.value )
86
+ input.value = ""
87
+ return false
88
+ }
89
+ }(document.getElementById('form'))
90
+ })()
91
+ }
92
+ </script>
93
+ </html>
94
+ HTML
95
+ end
96
+ end
97
+
98
+ options = {Host: "0.0.0.0", Port: 3000, max_connection: 16}
99
+ WebServer.new( options )
100
+
101
+ Signal.trap(:INT) do
102
+ exit
103
+ end
104
+
105
+ sleep
@@ -0,0 +1,94 @@
1
+ require 'sloth/reel'
2
+
3
+ class WebServer < Reel::Server::HTTP
4
+ include Celluloid::Internals::Logger
5
+
6
+ def initialize( options )
7
+ host = options[:Host] || "0.0.0.0"
8
+ port = options[:Port] || 3000
9
+ opts = {}
10
+ opts[:max_connection] = options[:max_connection] || 16
11
+
12
+ info "Reel::Server starting on #{host}:#{port}"
13
+ super( host, port, opts, &method(:on_connection) )
14
+ end
15
+
16
+ def on_connection(connection)
17
+ while request = connection.request
18
+ if request.websocket?
19
+ connection.detach
20
+ route_websocket( request )
21
+ else
22
+ route_request( connection, request )
23
+ end
24
+ end
25
+ end
26
+
27
+ def route_websocket( request )
28
+ case request.url
29
+ when "/timeinfo"
30
+ route_timeinfo( request )
31
+ end
32
+ end
33
+
34
+ def route_timeinfo(request)
35
+ if request.websocket?
36
+ Thread.start do
37
+ begin
38
+ websocket = request.websocket
39
+ while not websocket.closed?
40
+ str = Time.now.to_s
41
+ websocket.write str
42
+ sleep 1
43
+ end
44
+ rescue Reel::SocketError
45
+ end
46
+ end
47
+ end
48
+ end
49
+
50
+ def route_request(connection, request)
51
+ case request.url
52
+ when "/"
53
+ render_index(connection)
54
+ else
55
+ info "404 Not Found: #{request.path}"
56
+ connection.respond :not_found, "Not found"
57
+ end
58
+ end
59
+
60
+ def render_index(connection)
61
+ info "200 OK: /"
62
+ connection.respond :ok, <<-HTML
63
+ <!doctype html>
64
+ <html lang="en">
65
+ <head>
66
+ <meta charset="utf-8">
67
+ <title>Reel WebSockets Time Server Example</title>
68
+ </head>
69
+ <script>
70
+ var ws = new WebSocket('ws://' + window.location.host + '/timeinfo');
71
+ ws.onmessage = function(mesg){
72
+ document.getElementById('current-time').innerHTML = mesg.data;
73
+ }
74
+ </script>
75
+ <body>
76
+ <div id="content">
77
+ <h1>Reel WebSockets Time Server Example</h1>
78
+ <h2>The time is now: <span id="current-time">...</span></h2>
79
+ </div>
80
+ </body>
81
+ </html>
82
+ HTML
83
+ end
84
+ end
85
+
86
+ options = {Host: "0.0.0.0", Port: 3000, max_connection: 16}
87
+ WebServer.new( options )
88
+
89
+ Signal.trap(:INT) do
90
+ exit
91
+ end
92
+
93
+ sleep
94
+