websocket-rack 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,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