websocket-rack 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,65 @@
1
+ module Rack
2
+ module WebSocket
3
+ class Application
4
+
5
+ DEFAULT_OPTIONS = {}
6
+ attr_accessor :options
7
+
8
+ def on_open; end # Fired when a client is connected.
9
+ def on_message(msg); end # Fired when a message from a client is received.
10
+ def on_close; end # Fired when a client is disconnected.
11
+ def on_error(error); end # Fired when error occurs.
12
+
13
+ def initialize(*args)
14
+ app, options = args[0], args[1]
15
+ app, options = nil, app if app.is_a?(Hash)
16
+ @options = DEFAULT_OPTIONS.merge(options || {})
17
+ @app = app
18
+ end
19
+
20
+ def call(env)
21
+ if(env['HTTP_CONNECTION'].to_s.downcase == 'upgrade' && env['HTTP_UPGRADE'].to_s.downcase == 'websocket')
22
+ @env = env
23
+ socket = env['async.connection']
24
+ @connection = Connection.new(self, socket)
25
+ @connection.dispatch(env) ? async_response : failure_response
26
+ elsif @app
27
+ @app.call(env)
28
+ else
29
+ not_fount_response
30
+ end
31
+ end
32
+
33
+ def close_websocket
34
+ if @connection
35
+ @connection.close_websocket
36
+ else
37
+ raise WebSocketError, "WebSocket not opened"
38
+ end
39
+ end
40
+
41
+ def send_data(data)
42
+ if @connection
43
+ @connection.send data
44
+ else
45
+ raise WebSocketError, "WebSocket not opened"
46
+ end
47
+ end
48
+
49
+ protected
50
+
51
+ def async_response
52
+ [-1, {}, []]
53
+ end
54
+
55
+ def failure_response
56
+ [ 400, { "Content-Type" => "text/plain" }, [ 'invalid data' ] ]
57
+ end
58
+
59
+ def not_fount_response
60
+ [ 404, { "Content-Type" => "text/plain" }, [ 'not found' ] ]
61
+ end
62
+
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,105 @@
1
+ require 'addressable/uri'
2
+
3
+ module Rack
4
+ module WebSocket
5
+ class Connection
6
+ include Debugger
7
+
8
+ def initialize(app, socket, options = {})
9
+ @app = app
10
+ @socket = socket
11
+ @options = options
12
+ @debug = options[:debug] || false
13
+
14
+ socket.websocket = self
15
+
16
+ debug [:initialize]
17
+ end
18
+
19
+ def trigger_on_message(msg)
20
+ @app.on_message(msg)
21
+ end
22
+ def trigger_on_open
23
+ @app.on_open
24
+ end
25
+ def trigger_on_close
26
+ @app.on_close
27
+ end
28
+ def trigger_on_error(error)
29
+ @app.on_error(error)
30
+ end
31
+
32
+ def method_missing(sym, *args, &block)
33
+ @socket.send sym, *args, &block
34
+ end
35
+
36
+ # Use this method to close the websocket connection cleanly
37
+ # This sends a close frame and waits for acknowlegement before closing
38
+ # the connection
39
+ def close_websocket
40
+ if @handler
41
+ @handler.close_websocket
42
+ else
43
+ # The handshake hasn't completed - should be safe to terminate
44
+ close_connection
45
+ end
46
+ end
47
+
48
+ def receive_data(data)
49
+ debug [:receive_data, data]
50
+
51
+ @handler.receive_data(data)
52
+ end
53
+
54
+ def unbind
55
+ debug [:unbind, :connection]
56
+
57
+ @handler.unbind if @handler
58
+ end
59
+
60
+ def dispatch(data)
61
+ debug [:inbound_headers, data]
62
+ @handler = HandlerFactory.build(self, data, @debug)
63
+ unless @handler
64
+ # The whole header has not been received yet.
65
+ return false
66
+ end
67
+ @handler.run
68
+ return true
69
+ rescue => e
70
+ debug [:error, e]
71
+ process_bad_request(e)
72
+ return false
73
+ end
74
+
75
+ def process_bad_request(reason)
76
+ trigger_on_error(reason)
77
+ send_data "HTTP/1.1 400 Bad request\r\n\r\n"
78
+ close_connection_after_writing
79
+ end
80
+
81
+ def send(data)
82
+ debug [:send, data]
83
+
84
+ if @handler
85
+ @handler.send_text_frame(data)
86
+ else
87
+ raise WebSocketError, "Cannot send data before onopen callback"
88
+ end
89
+ end
90
+
91
+ def close_with_error(message)
92
+ trigger_on_error(message)
93
+ close_connection_after_writing
94
+ end
95
+
96
+ def request
97
+ @handler ? @handler.request : {}
98
+ end
99
+
100
+ def state
101
+ @handler ? @handler.state : :handshake
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,17 @@
1
+ module Rack
2
+ module WebSocket
3
+ module Debugger
4
+
5
+ private
6
+
7
+ def debug(*data)
8
+ if @debug
9
+ require 'pp'
10
+ pp data
11
+ puts
12
+ end
13
+ end
14
+
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,71 @@
1
+ module Rack
2
+ module WebSocket
3
+ module Extensions
4
+ module Thin
5
+ module Connection
6
+
7
+ def self.included(thin_conn)
8
+ thin_conn.class_eval do
9
+ alias :pre_process_without_websocket :pre_process
10
+ alias :pre_process :pre_process_with_websocket
11
+
12
+ alias :receive_data_without_websocket :receive_data
13
+ alias :receive_data :receive_data_with_websocket
14
+
15
+ alias :unbind_without_websocket :unbind
16
+ alias :unbind :unbind_with_websocket
17
+
18
+ alias :receive_data_without_flash_policy_file :receive_data
19
+ alias :receive_data :receive_data_with_flash_policy_file
20
+ end
21
+ end
22
+
23
+ attr_accessor :websocket
24
+
25
+ def websocket?
26
+ !self.websocket.nil?
27
+ end
28
+
29
+ def pre_process_with_websocket
30
+ @request.env['async.connection'] = self
31
+ pre_process_without_websocket
32
+ end
33
+
34
+ def receive_data_with_websocket(data)
35
+ if self.websocket?
36
+ self.websocket.receive_data(data)
37
+ else
38
+ receive_data_without_websocket(data)
39
+ end
40
+ end
41
+
42
+ def unbind_with_websocket
43
+ if self.websocket?
44
+ self.websocket.unbind
45
+ else
46
+ unbind_without_websocket
47
+ end
48
+ end
49
+
50
+ def receive_data_with_flash_policy_file(data)
51
+ # thin require data to be proper http request - in it's not
52
+ # then @request.parse raises exception and data isn't parsed
53
+ # by futher methods. Here we only check if it is flash
54
+ # policy file request ("<policy-file-request/>\000") and
55
+ # if so then flash policy file is returned. if not then
56
+ # rest of request is handled.
57
+ if (data == "<policy-file-request/>\000")
58
+ file = '<?xml version="1.0"?><cross-domain-policy><allow-access-from domain="*" to-ports="*"/></cross-domain-policy>'
59
+ # ignore errors - we will close this anyway
60
+ send_data(file) rescue nil
61
+ close_connection_after_writing
62
+ else
63
+ receive_data_without_flash_policy_file(data)
64
+ end
65
+ end
66
+
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,16 @@
1
+ module Rack
2
+ module WebSocket
3
+ module Extensions
4
+ module Thin
5
+
6
+ autoload :Connection, "#{::File.dirname(__FILE__)}/thin/connection"
7
+
8
+ def self.included(thin)
9
+ thin_connection = thin.const_get(:Connection)
10
+ thin_connection.send(:include, Thin.const_get(:Connection))
11
+ end
12
+
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,178 @@
1
+ # encoding: BINARY
2
+
3
+ module Rack
4
+ module WebSocket
5
+ module Framing03
6
+
7
+ def initialize_framing
8
+ @data = ''
9
+ @application_data_buffer = '' # Used for MORE frames
10
+ end
11
+
12
+ def process_data(newdata)
13
+ error = false
14
+
15
+ while !error && @data.size > 1
16
+ pointer = 0
17
+
18
+ more = (@data.getbyte(pointer) & 0b10000000) == 0b10000000
19
+ # Ignoring rsv1-3 for now
20
+ opcode = @data.getbyte(0) & 0b00001111
21
+ pointer += 1
22
+
23
+ # Ignoring rsv4
24
+ length = @data.getbyte(pointer) & 0b01111111
25
+ pointer += 1
26
+
27
+ payload_length = case length
28
+ when 127 # Length defined by 8 bytes
29
+ # Check buffer size
30
+ if @data.getbyte(pointer+8-1) == nil
31
+ debug [:buffer_incomplete, @data.inspect]
32
+ error = true
33
+ next
34
+ end
35
+
36
+ # Only using the last 4 bytes for now, till I work out how to
37
+ # unpack 8 bytes. I'm sure 4GB frames will do for now :)
38
+ l = @data[(pointer+4)..(pointer+7)].unpack('N').first
39
+ pointer += 8
40
+ l
41
+ when 126 # Length defined by 2 bytes
42
+ # Check buffer size
43
+ if @data.getbyte(pointer+2-1) == nil
44
+ debug [:buffer_incomplete, @data.inspect]
45
+ error = true
46
+ next
47
+ end
48
+
49
+ l = @data[pointer..(pointer+1)].unpack('n').first
50
+ pointer += 2
51
+ l
52
+ else
53
+ length
54
+ end
55
+
56
+ # Check buffer size
57
+ if @data.getbyte(pointer+payload_length-1) == nil
58
+ debug [:buffer_incomplete, @data.inspect]
59
+ error = true
60
+ next
61
+ end
62
+
63
+ # Throw away data up to pointer
64
+ @data.slice!(0...pointer)
65
+
66
+ # Read application data
67
+ application_data = @data.slice!(0...payload_length)
68
+
69
+ frame_type = opcode_to_type(opcode)
70
+
71
+ if frame_type == :continuation && !@frame_type
72
+ raise WebSocketError, 'Continuation frame not expected'
73
+ end
74
+
75
+ if more
76
+ debug [:moreframe, frame_type, application_data]
77
+ @application_data_buffer << application_data
78
+ @frame_type = frame_type
79
+ else
80
+ # Message is complete
81
+ if frame_type == :continuation
82
+ @application_data_buffer << application_data
83
+ message(@frame_type, '', @application_data_buffer)
84
+ @application_data_buffer = ''
85
+ @frame_type = nil
86
+ else
87
+ message(frame_type, '', application_data)
88
+ end
89
+ end
90
+ end # end while
91
+ end
92
+
93
+ def send_frame(frame_type, application_data)
94
+ if @state == :closing && data_frame?(frame_type)
95
+ raise WebSocketError, "Cannot send data frame since connection is closing"
96
+ end
97
+
98
+ frame = ''
99
+
100
+ opcode = type_to_opcode(frame_type)
101
+ byte1 = opcode # since more, rsv1-3 are 0
102
+ frame << byte1
103
+
104
+ length = application_data.size
105
+ if length <= 125
106
+ byte2 = length # since rsv4 is 0
107
+ frame << byte2
108
+ elsif length < 65536 # write 2 byte length
109
+ frame << 126
110
+ frame << [length].pack('n')
111
+ else # write 8 byte length
112
+ frame << 127
113
+ frame << [length >> 32, length & 0xFFFFFFFF].pack("NN")
114
+ end
115
+
116
+ frame << application_data
117
+
118
+ @connection.send_data(frame)
119
+ end
120
+
121
+ def send_text_frame(data)
122
+ send_frame(:text, data)
123
+ end
124
+
125
+ private
126
+
127
+ def message(message_type, extension_data, application_data)
128
+ case message_type
129
+ when :close
130
+ if @state == :closing
131
+ # TODO: Check that message body matches sent data
132
+ # We can close connection immediately since there is no more data
133
+ # is allowed to be sent or received on this connection
134
+ @connection.close_connection
135
+ @state = :closed
136
+ else
137
+ # Acknowlege close
138
+ # The connection is considered closed
139
+ send_frame(:close, application_data)
140
+ @state = :closed
141
+ @connection.close_connection_after_writing
142
+ end
143
+ when :ping
144
+ # Pong back the same data
145
+ send_frame(:pong, application_data)
146
+ when :pong
147
+ # TODO: Do something. Complete a deferrable established by a ping?
148
+ when :text, :binary
149
+ @connection.trigger_on_message(application_data)
150
+ end
151
+ end
152
+
153
+ FRAME_TYPES = {
154
+ :continuation => 0,
155
+ :close => 1,
156
+ :ping => 2,
157
+ :pong => 3,
158
+ :text => 4,
159
+ :binary => 5
160
+ }
161
+ FRAME_TYPES_INVERSE = FRAME_TYPES.invert
162
+ # Frames are either data frames or control frames
163
+ DATA_FRAMES = [:text, :binary, :continuation]
164
+
165
+ def type_to_opcode(frame_type)
166
+ FRAME_TYPES[frame_type] || raise("Unknown frame type")
167
+ end
168
+
169
+ def opcode_to_type(opcode)
170
+ FRAME_TYPES_INVERSE[opcode] || raise("Unknown opcode")
171
+ end
172
+
173
+ def data_frame?(type)
174
+ DATA_FRAMES.include?(type)
175
+ end
176
+ end
177
+ end
178
+ end