plezi 0.9.2 → 0.10.1

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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +30 -0
  3. data/README.md +44 -31
  4. data/bin/plezi +3 -3
  5. data/lib/plezi.rb +21 -43
  6. data/lib/plezi/common/defer.rb +21 -0
  7. data/lib/plezi/common/dsl.rb +115 -91
  8. data/lib/plezi/common/redis.rb +44 -0
  9. data/lib/plezi/common/settings.rb +58 -0
  10. data/lib/plezi/handlers/controller_core.rb +132 -0
  11. data/lib/plezi/handlers/controller_magic.rb +85 -259
  12. data/lib/plezi/handlers/http_router.rb +139 -60
  13. data/lib/plezi/handlers/route.rb +9 -178
  14. data/lib/plezi/handlers/stubs.rb +2 -2
  15. data/lib/plezi/helpers/http_sender.rb +72 -0
  16. data/lib/plezi/helpers/magic_helpers.rb +12 -0
  17. data/lib/plezi/{server → helpers}/mime_types.rb +0 -0
  18. data/lib/plezi/version.rb +1 -1
  19. data/plezi.gemspec +3 -11
  20. data/resources/Gemfile +20 -21
  21. data/resources/controller.rb +2 -2
  22. data/resources/oauth_config.rb +1 -1
  23. data/resources/redis_config.rb +2 -0
  24. data/test/plezi_tests.rb +39 -46
  25. metadata +24 -33
  26. data/lib/plezi/common/logging.rb +0 -60
  27. data/lib/plezi/eventmachine/connection.rb +0 -190
  28. data/lib/plezi/eventmachine/em.rb +0 -98
  29. data/lib/plezi/eventmachine/io.rb +0 -272
  30. data/lib/plezi/eventmachine/protocol.rb +0 -54
  31. data/lib/plezi/eventmachine/queue.rb +0 -51
  32. data/lib/plezi/eventmachine/ssl_connection.rb +0 -144
  33. data/lib/plezi/eventmachine/timers.rb +0 -117
  34. data/lib/plezi/eventmachine/workers.rb +0 -33
  35. data/lib/plezi/handlers/http_echo.rb +0 -27
  36. data/lib/plezi/handlers/http_host.rb +0 -214
  37. data/lib/plezi/handlers/magic_helpers.rb +0 -32
  38. data/lib/plezi/server/http.rb +0 -129
  39. data/lib/plezi/server/http_protocol.rb +0 -319
  40. data/lib/plezi/server/http_request.rb +0 -146
  41. data/lib/plezi/server/http_response.rb +0 -319
  42. data/lib/plezi/server/websocket.rb +0 -251
  43. data/lib/plezi/server/websocket_client.rb +0 -178
  44. data/lib/plezi/server/ws_response.rb +0 -161
@@ -1,251 +0,0 @@
1
- module Plezi
2
-
3
- # this module is the protocol (controller) for the HTTP server.
4
- #
5
- #
6
- # to do: implemet logging, support body types: multipart (non-ASCII form data / uploaded files), json & xml
7
- class WSProtocol < EventMachine::Protocol
8
-
9
- SUPPORTED_EXTENTIONS = {}
10
- # SUPPORTED_EXTENTIONS['x-webkit-deflate-frame'] = Proc.new {|body, params| }
11
- # SUPPORTED_EXTENTIONS['permessage-deflate'] = Proc.new {|body, params| } # client_max_window_bits
12
-
13
- # get the timeout interval for this websockt (the number of seconds the socket can remain with no activity - will be reset every ping, message etc').
14
- def timeout_interval
15
- connection.timeout
16
- end
17
- # set the timeout interval for this websockt (the number of seconds the socket can remain with no activity - will be reset every ping, message etc').
18
- def timeout_interval= value
19
- connection.timeout = value
20
- end
21
-
22
- # the extentions registered for the websockets connection.
23
- attr_reader :extentions
24
-
25
- def initialize connection, params
26
- super
27
- @extentions = []
28
- @locker = Mutex.new
29
- @parser_stage = 0
30
- @parser_data = {}
31
- @parser_data[:body] = []
32
- @parser_data[:step] = 0
33
- @message = ''
34
- end
35
-
36
- # a proc object that calls #on_connect for the handler passed.
37
- ON_CONNECT_PROC = Proc.new {|handler| handler.on_connect}
38
- # called when connection is initialized.
39
- def on_connect
40
- # set timeout to 60 seconds
41
- Plezi.log_raw "#{@request[:client_ip]} [#{Time.now.utc}] - #{@connection.object_id} Upgraded HTTP to WebSockets.\n"
42
- Plezi::EventMachine.queue [@connection.handler], ON_CONNECT_PROC if @connection.handler && @connection.handler.methods.include?(:on_connect)
43
- @connection.touch
44
- Plezi.run_after(2) { @connection.timeout = 60 }
45
- end
46
-
47
- # called when data is recieved
48
- # returns an Array with any data not yet processed (to be returned to the in-que).
49
- def on_message
50
- # parse the request
51
- extract_message connection.read.to_s.bytes
52
- true
53
- end
54
-
55
- # a proc object that calls #on_disconnect for the handler passed.
56
- ON_DISCONNECT_PROC = Proc.new {|handler| handler.on_disconnect}
57
- # called when a disconnect is fired
58
- # (socket was disconnected / connection should be disconnected / shutdown / socket error)
59
- def on_disconnect
60
- # Plezi.log_raw "#{@request[:client_ip]} [#{Time.now.utc}] - #{@connection.object_id} Websocket disconnected.\n"
61
- Plezi::EventMachine.queue [@connection.handler], ON_DISCONNECT_PROC if @connection.handler.methods.include?(:on_disconnect)
62
- end
63
-
64
- ########
65
- # Protocol Specific Helpers
66
-
67
- # perform the HTTP handshake for WebSockets. send a 400 Bad Request error if handshake fails.
68
- def http_handshake request, response, handler
69
- # review handshake (version, extentions)
70
- # should consider adopting the websocket gem for handshake and framing:
71
- # https://github.com/imanel/websocket-ruby
72
- # http://www.rubydoc.info/github/imanel/websocket-ruby
73
- return connection.handler.hosts[request[:host] || :default].send_by_code request, 400 , response.headers.merge('sec-websocket-extensions' => SUPPORTED_EXTENTIONS.keys.join(', ')) unless request['upgrade'].to_s.downcase == 'websocket' &&
74
- request['sec-websocket-key'] &&
75
- request['connection'].to_s.downcase == 'upgrade' &&
76
- # (request['sec-websocket-extensions'].split(/[\s]*[,][\s]*/).reject {|ex| ex == '' || SUPPORTED_EXTENTIONS[ex.split(/[\s]*;[\s]*/)[0]] } ).empty? &&
77
- (request['sec-websocket-version'].to_s.downcase.split(/[, ]/).map {|s| s.strip} .include?( '13' ))
78
- @request = request
79
- response.status = 101
80
- response['upgrade'] = 'websocket'
81
- response['content-length'] = '0'
82
- response['connection'] = 'Upgrade'
83
- response['sec-websocket-version'] = '13'
84
- # Note that the client is only offering to use any advertised extensions
85
- # and MUST NOT use them unless the server indicates that it wishes to use the extension.
86
- request['sec-websocket-extensions'].to_s.split(/[\s]*[,][\s]*/).each {|ex| @extentions << ex.split(/[\s]*;[\s]*/) if SUPPORTED_EXTENTIONS[ex.split(/[\s]*;[\s]*/)[0]]}
87
- response['sec-websocket-extensions'] = @extentions.map {|e| e[0] } .join (',')
88
- response.headers.delete 'sec-websocket-extensions' if response['sec-websocket-extensions'].empty?
89
- response['Sec-WebSocket-Accept'] = Digest::SHA1.base64digest(request['sec-websocket-key'] + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')
90
- response.finish
91
- @extentions.freeze
92
- connection.protocol = self
93
- connection.handler = handler
94
- Plezi::EventMachine.queue [self], ON_CONNECT_PROC
95
- return true
96
- end
97
-
98
- # parse the message and send it to the handler
99
- #
100
- # test: frame = ["819249fcd3810b93b2fb69afb6e62c8af3e83adc94ee2ddd"].pack("H*").bytes; @parser_stage = 0; @parser_data = {}
101
- # accepts:
102
- # frame:: an array of bytes
103
- def extract_message data
104
- until data.empty?
105
- if @parser_stage == 0 && !data.empty?
106
- @parser_data[:fin] = data[0][7] == 1
107
- @parser_data[:rsv1] = data[0][6] == 1
108
- @parser_data[:rsv2] = data[0][5] == 1
109
- @parser_data[:rsv3] = data[0][4] == 1
110
- @parser_data[:op_code] = data[0] & 0b00001111
111
- @parser_op_code ||= data[0] & 0b00001111
112
- @parser_stage += 1
113
- data.shift
114
- end
115
- if @parser_stage == 1
116
- @parser_data[:mask] = data[0][7]
117
- @parser_data[:len] = data[0] & 0b01111111
118
- data.shift
119
- if @parser_data[:len] == 126
120
- @parser_data[:len] = merge_bytes( *(data.slice!(0,2)) ) # should be = ?
121
- elsif @parser_data[:len] == 127
122
- len = 0
123
- @parser_data[:len] = merge_bytes( *(data.slice!(0,8)) ) # should be = ?
124
- end
125
- @parser_data[:step] = 0
126
- @parser_stage += 1
127
- review_message_size
128
- end
129
- if @parser_stage == 2 && @parser_data[:mask] == 1
130
- @parser_data[:mask_key] = data.slice!(0,4)
131
- @parser_stage += 1
132
- elsif @parser_data[:mask] != 1
133
- @parser_stage += 1
134
- end
135
- if @parser_stage == 3 && @parser_data[:step] < @parser_data[:len]
136
- # data.length.times {|i| data[0] = data[0] ^ @parser_data[:mask_key][@parser_data[:step] % 4] if @parser_data[:mask_key]; @parser_data[:step] += 1; @parser_data[:body] << data.shift; break if @parser_data[:step] == @parser_data[:len]}
137
- slice_length = [data.length, (@parser_data[:len]-@parser_data[:step])].min
138
- if @parser_data[:mask_key]
139
- masked = data.slice!(0, slice_length)
140
- masked.map!.with_index {|b, i| b ^ @parser_data[:mask_key][ ( i + @parser_data[:step] ) % 4] }
141
- @parser_data[:body].concat masked
142
- else
143
- @parser_data[:body].concat data.slice!(0, slice_length)
144
- end
145
- @parser_data[:step] += slice_length
146
- end
147
- complete_frame unless @parser_data[:step] < @parser_data[:len]
148
- end
149
- true
150
- end
151
-
152
- # takes and Array of bytes and combines them to an int(16 Bit), 32Bit or 64Bit number
153
- def merge_bytes *bytes
154
- return bytes.pop if bytes.length == 1
155
- bytes.pop ^ (merge_bytes(*bytes) << 8)
156
- end
157
-
158
- # The proc queued whenever a frame is complete.
159
- COMPLETE_FRAME_PROC = Proc.new {|handler, message| handler.on_message message}
160
-
161
- # handles the completed frame and sends a message to the handler once all the data has arrived.
162
- def complete_frame
163
- @extentions.each {|ex| SUPPORTED_EXTENTIONS[ex[0]][1].call(@parser_data[:body], ex[1..-1]) if SUPPORTED_EXTENTIONS[ex[0]]}
164
-
165
- case @parser_data[:op_code]
166
- when 9 # ping
167
- # handle @parser_data[:op_code] == 9 (ping)
168
- Plezi.callback @connection, :send_nonblock, WSResponse.frame_data(@parser_data[:body].pack('C*'), 10) # "\x8A\x00" can't be used, because body should be returned. # sends pong op_code == 10
169
- @parser_op_code = nil if @parser_op_code == 9
170
- when 10 #pong
171
- # handle @parser_data[:op_code] == 10 (pong)
172
- @parser_op_code = nil if @parser_op_code == 10
173
- when 8
174
- # handle @parser_data[:op_code] == 8 (close)
175
- Plezi.callback( @connection, :send_nonblock, "\x88\x00" ) { @connection.disconnect }
176
- @parser_op_code = nil if @parser_op_code == 8
177
- else
178
- @message << @parser_data[:body].pack('C*')
179
- # handle @parser_data[:op_code] == 0 / fin == false (continue a frame that hasn't ended yet)
180
- if @parser_data[:fin]
181
- HTTP.make_utf8! @message if @parser_op_code == 1
182
- Plezi::EventMachine.queue [@connection.handler, @message], COMPLETE_FRAME_PROC
183
- @message = ''
184
- @parser_op_code = nil
185
- end
186
- end
187
- @parser_stage = 0
188
- @parser_data[:body].clear
189
- @parser_data[:step] = 0
190
- end
191
- #reviews the message size and closes the connection if expected message size is over the allowed limit.
192
- def review_message_size
193
- if ( self.class.message_size_limit.to_i > 0 ) && ( ( @parser_data[:len] + @message.bytesize ) > self.class.message_size_limit.to_i )
194
- Plezi.callback @connection, :disconnect
195
- @message.clear
196
- @parser_data[:step] = 0
197
- @parser_data[:body].clear
198
- @parser_stage = -1
199
- return false
200
- end
201
- true
202
- end
203
-
204
- # Sets the message byte size limit for a Websocket message. Defaults to 0 (no limit)
205
- #
206
- # Although memory will be allocated for the latest TCP/IP frame,
207
- # this allows the websocket to disconnect if the incoming expected message size exceeds the allowed maximum size.
208
- #
209
- # If the sessage size limit is exceeded, the disconnection will be immidiate as an attack will be assumed. The protocol's normal disconnect sequesnce will be discarded.
210
- def self.message_size_limit=val
211
- @message_size_limit = val
212
- end
213
- # Gets the message byte size limit for a Websocket message. Defaults to 0 (no limit)
214
- def self.message_size_limit
215
- @message_size_limit
216
- end
217
- message_size_limit = 0
218
-
219
- end
220
-
221
- # Sets the message byte size limit for a Websocket message. Defaults to 0 (no limit)
222
- #
223
- # Although memory will be allocated for the latest TCP/IP frame,
224
- # this allows the websocket to disconnect if the incoming expected message size exceeds the allowed maximum size.
225
- #
226
- # If the sessage size limit is exceeded, the disconnection will be immidiate as an attack will be assumed. The protocol's normal disconnect sequesnce will be discarded.
227
- def self.ws_message_size_limit=val
228
- WSProtocol.message_size_limit = val
229
- end
230
- # Gets the message byte size limit for a Websocket message. Defaults to 0 (no limit)
231
- def self.ws_message_size_limit
232
- WSProtocol.message_size_limit
233
- end
234
- end
235
-
236
-
237
- ######
238
- ## example requests
239
-
240
- # GET /nickname HTTP/1.1
241
- # Upgrade: websocket
242
- # Connection: Upgrade
243
- # Host: localhost:3000
244
- # Origin: https://www.websocket.org
245
- # Cookie: test=my%20cookies; user_token=2INa32_vDgx8Aa1qe43oILELpSdIe9xwmT8GTWjkS-w
246
- # Pragma: no-cache
247
- # Cache-Control: no-cache
248
- # Sec-WebSocket-Key: 1W9B64oYSpyRL/yuc4k+Ww==
249
- # Sec-WebSocket-Version: 13
250
- # Sec-WebSocket-Extensions: x-webkit-deflate-frame
251
- # User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10) AppleWebKit/600.1.25 (KHTML, like Gecko) Version/8.0 Safari/600.1.25
@@ -1,178 +0,0 @@
1
- module Plezi
2
-
3
- # Websocket client objects are members of this class.
4
- #
5
- # This is a VERY simple Websocket client. It doesn't support cookies, HTTP authentication or... well... anything, really.
6
- # It's just a simple client used for the Plezi framework's testing. It's usful for simple WebSocket connections, but no more.
7
- class WebsocketClient
8
- attr_accessor :response, :request
9
-
10
- class RequestEmulator < Hash
11
- def service
12
- self[:connection]
13
- end
14
- end
15
-
16
- def initialize request
17
- @response = WSResponse.new request
18
- @options = request[:options]
19
- @on_message = @options[:on_message]
20
- raise "Websocket client must have an #on_message Proc." unless @on_message && @on_message.is_a?(Proc)
21
- @on_connect = @options[:on_connect]
22
- @on_disconnect = @options[:on_disconnect]
23
- end
24
-
25
- def on_message(data = false, &block)
26
- unless data
27
- @on_message = block if block
28
- return @on_message
29
- end
30
- instance_exec( data, &@on_message)
31
- end
32
-
33
- def on_connect(&block)
34
- if block
35
- @on_connect = block
36
- return @on_connect
37
- end
38
- instance_exec(&@on_connect) if @on_connect
39
- end
40
-
41
- def on_disconnect(&block)
42
- if block
43
- @on_disconnect = block
44
- return @on_disconnect
45
- end
46
- instance_exec(&@on_disconnect) if @on_disconnect
47
- end
48
-
49
- #disconnects the Websocket.
50
- def disconnect
51
- @response.close if @response
52
- end
53
- alias :close :disconnect
54
-
55
- # Asynchronously sends data through the socket. a shortcut for ws_client.response <<
56
- def << data
57
- @response << data
58
- end
59
-
60
- # Synchronously sends data through the socket. a shortcut for ws_client.response <<
61
- def send data
62
- @response.send data
63
- end
64
-
65
- # Create a simple Websocket Client(!)
66
- #
67
- # This method accepts two parameters:
68
- # url:: a String representing the URL of the websocket. i.e.: 'ws://foo.bar.com:80/ws/path'
69
- # options:: a Hash with options to be used. The options will be used to define
70
- # &block:: an optional block that accepts one parameter (data) and will be used as the `#on_message(data)`
71
- #
72
- # The method will either return a WebsocketClient instance object or it will raise an exception.
73
- #
74
- # An on_message Proc must be defined, or the method will fail.
75
- #
76
- # The on_message Proc can be defined using the optional block:
77
- #
78
- # WebsocketClient.connect_to("ws://localhost:3000/") {|data| response << data} #echo example
79
- #
80
- # OR, the on_message Proc can be defined using the options Hash:
81
- #
82
- # WebsocketClient.connect_to("ws://localhost:3000/", on_connect: -> {}, on_message: -> {|data| response << data})
83
- #
84
- # The #on_message(data), #on_connect and #on_disconnect methods will be executed within the context of the WebsocketClient
85
- # object, and will have natice acess to the Websocket response object.
86
- #
87
- # After the WebsocketClient had been created, it's possible to update the #on_message and #on_disconnect methods:
88
- #
89
- # # updates #on_message
90
- # wsclient.on_message do |data|
91
- # response << "I'll disconnect on the next message!"
92
- # # updates #on_message again.
93
- # on_message {|data| disconnect }
94
- # end
95
- #
96
- #
97
- # !!please be aware that the Websockt Client will not attempt to verify SSL certificates,
98
- # so that even SSL connections are subject to a possible man in the middle attack.
99
- def self.connect_to url, options={}, &block
100
- options[:on_message] ||= block
101
- options[:handler] = WebsocketClient
102
- options[:protocol] = EventMachine::Protocol
103
- url = URI.parse(url) unless url.is_a?(URI)
104
- connection_type = EventMachine::Connection
105
-
106
- socket = false #implement the connection, ssl vs. no ssl
107
- if url.scheme == "https" || url.scheme == "wss"
108
- connection_type = EventMachine::SSLConnection
109
- options[:ssl_client] = true
110
- url.port ||= 443
111
- end
112
- url.port ||= 80
113
- socket = TCPSocket.new(url.host, url.port)
114
- connection = connection_type.new socket, options
115
- psedo_request = RequestEmulator.new
116
- psedo_request[:connection] = connection
117
- psedo_request[:client_ip] = 'WS Client'
118
- psedo_request[:url] = url
119
- psedo_request[:options] = options
120
- WSProtocol.client_handshake psedo_request
121
- connection.handler
122
- rescue => e
123
- socket.close if socket
124
- raise e
125
- end
126
-
127
- end
128
-
129
- class WSProtocol < EventMachine::Protocol
130
- def self.client_handshake psedo_request, timeout = 5
131
- connection = psedo_request[:connection]
132
- url = psedo_request[:url]
133
- # send protocol upgrade request
134
- websocket_key = [(Array.new(16) {rand 255} .pack 'c*' )].pack('m0*')
135
- connection.send "GET #{url.path}#{url.query.to_s.empty? ? '' : ('?' + url.query)} HTTP/1.1\r\nHost: #{url.host}#{url.port ? (':'+url.port.to_s) : ''}\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Key: #{websocket_key}\r\nSec-WebSocket-Version: 13\r\n\r\n"
136
- # wait for answer - make sure we don't over-read
137
- # (a websocket message might be sent immidiately after connection is established)
138
- reply = ''
139
- reply.force_encoding('binary')
140
- start_time = Time.now
141
- stop_reply = "\r\n\r\n"
142
- until reply[-4..-1] == stop_reply
143
- (reply << connection.read(1)) rescue (sleep 0.1)
144
- raise Timeout::Error, "Websocket client handshake timed out (HTTP reply not recieved)\n\n Got Only: #{reply.dump}" if Time.now >= (start_time + 5)
145
- end
146
- # review reply
147
- raise 'Connection Refused.' unless reply.lines[0].match(/^HTTP\/[\d\.]+ 101/i)
148
- raise 'Websocket Key Authentication failed.' unless reply.match(/^Sec-WebSocket-Accept:[\s]*([^\s]*)/i) && reply.match(/^Sec-WebSocket-Accept:[\s]*([^\s]*)/i)[1] == Digest::SHA1.base64digest(websocket_key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')
149
- # set-up handler response object.
150
- connection.handler = WebsocketClient.new psedo_request
151
-
152
- # raise "not yet implemented"
153
-
154
- # set the connetion's protocol to a new WSProtocol instance
155
- connection.protocol = self.new psedo_request[:connection], psedo_request[:options]
156
- # add the socket to the EventMachine IO reactor
157
- EventMachine.add_io connection.socket, connection
158
- true
159
- end
160
- end
161
- end
162
-
163
-
164
- ######
165
- ## example requests
166
-
167
- # GET /nickname HTTP/1.1
168
- # Upgrade: websocket
169
- # Connection: Upgrade
170
- # Host: localhost:3000
171
- # Origin: https://www.websocket.org
172
- # Cookie: test=my%20cookies; user_token=2INa32_vDgx8Aa1qe43oILELpSdIe9xwmT8GTWjkS-w
173
- # Pragma: no-cache
174
- # Cache-Control: no-cache
175
- # Sec-WebSocket-Key: 1W9B64oYSpyRL/yuc4k+Ww==
176
- # Sec-WebSocket-Version: 13
177
- # Sec-WebSocket-Extensions: x-webkit-deflate-frame
178
- # User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10) AppleWebKit/600.1.25 (KHTML, like Gecko) Version/8.0 Safari/600.1.25
@@ -1,161 +0,0 @@
1
- module Plezi
2
-
3
- # this class handles WebSocket response.
4
- #
5
- # the WSResponse supports only one method - the send method.
6
- #
7
- # use: `response << data` to send data. data should be a String object.
8
- #
9
- # the data wil be sent as text if the string is encoded as a UTF-8 string (default encoding).
10
- # otherwise, the data will be sent as a binary stream.
11
- #
12
- # todo: extentions support, support frames longer then 125 bytes.
13
- class WSResponse
14
-
15
- #the service through which the response will be sent.
16
- attr_reader :service
17
- #the request.
18
- attr_accessor :request
19
-
20
- # Sets the defalt Websockt auto-ping interval.
21
- #
22
- # The default ping interval is 45 seconds.
23
- #
24
- # It's possible to set the ping interval to false, thereby disabling auto-pinging.
25
- def self.ping_interval=(val)
26
- @ping_interval = val
27
- end
28
- # Returns the defalt Websockt auto-ping interval.
29
- #
30
- # Plezi will automatically send a ping frame to keep websocket connections open.
31
- # This auto-pinging can be disabled by setting the `ping_interval` to false.
32
- def self.ping_interval
33
- @ping_interval ||= 45
34
- end
35
- PING_PROC = Proc.new {|res| EventMachine.timed_job ping_interval, 1, [res.ping], PING_PROC unless res.service.disconnected? || !ping_interval }
36
-
37
- def initialize request
38
- @request, @service = request,request.service
39
- PING_PROC.call(self)
40
- end
41
-
42
- # sends data through the websocket connection in a non-blocking way.
43
- #
44
- # Plezi will try a best guess at the type of the data (binary vs. clear text).
45
- #
46
- # This should be the preferred way.
47
- def << str
48
- service.send_nonblock self.class.frame_data(str)
49
- self
50
- end
51
-
52
- # sends data through the websocket connection in a blocking way.
53
- #
54
- # Plezi will try a best guess at the type of the data (binary vs. clear text).
55
- #
56
- def send str
57
- service.send self.class.frame_data(str)
58
- self
59
- end
60
- # sends binary data through the websocket connection in a blocking way.
61
- #
62
- def binsend str
63
- service.send self.class.frame_data(str, 2)
64
- self
65
- end
66
- # sends clear text data through the websocket connection in a blocking way.
67
- #
68
- def txtsend str
69
- service.send self.class.frame_data(str, 1)
70
- self
71
- end
72
-
73
-
74
- # makes sure any data held in the buffer is actually sent.
75
- def flush
76
- service.flush
77
- self
78
- end
79
-
80
- # pings the connection
81
- def ping
82
- service.send_nonblock "\x89\x00" # op_code 9
83
- self
84
- end
85
- # pings the connection
86
- def pong
87
- service.send_nonblock "\x8A\x00" # op_code 10
88
- self
89
- end
90
-
91
- # a closeing Proc
92
- CLOSE_PROC = Proc.new {|c| c.send "\x88\x00"; c.close}
93
-
94
- # sends any pending data and closes the connection.
95
- def close
96
- service.locker.locked? ? (EventMachine.queue [service], CLOSE_PROC) : (CLOSE_PROC.call(service))
97
- end
98
-
99
- FRAME_SIZE_LIMIT = 131_072 # javascript to test: str = '0123456789'; bigstr = ""; for(i = 0; i<=1033200; i+=1) {bigstr += str}; ws = new WebSocket('ws://localhost:3000/ws/size') ; ws.onmessage = function(e) {console.log(e.data.length)};ws. onopen = function(e) {ws.send(bigstr)}
100
-
101
- # Dangerzone! use `send` instead: formats the data as one or more WebSocket frames.
102
- def self.frame_data data, op_code = nil, fin = true
103
- # set up variables
104
- frame = ''.force_encoding('binary')
105
- op_code ||= (data.encoding.name == 'UTF-8' ? 1 : 2)
106
-
107
-
108
- if data[FRAME_SIZE_LIMIT] && fin
109
- # fragment big data chuncks into smaller frames - op-code reset for 0 for all future frames.
110
- data = data.dup
111
- data.force_encoding('binary')
112
- [frame << frame_data(data.slice!(0...FRAME_SIZE_LIMIT), op_code, false), op_code = 0] while data.length > FRAME_SIZE_LIMIT # 1048576
113
- # frame << frame_data(data.slice!(0..1048576), op_code, false)
114
- # data =
115
- # op_code = 0
116
- end
117
-
118
- # apply extenetions to the frame
119
- ext = 0
120
- # ext |= call each service.protocol.extenetions with data #changes data and returns flags to be set
121
- # service.protocol.extenetions.each { |ex| ext |= WSProtocol::SUPPORTED_EXTENTIONS[ex[0]][2].call data, ex[1..-1]}
122
-
123
- # set
124
- frame << ( (fin ? 0b10000000 : 0) | (op_code & 0b00001111) | ext).chr
125
-
126
- if data.length < 125
127
- frame << data.length.chr
128
- elsif data.length.bit_length <= 16
129
- frame << 126.chr
130
- frame << [data.length].pack('S>')
131
- else
132
- frame << 127.chr
133
- frame << [data.length].pack('Q>')
134
- end
135
- frame.force_encoding(data.encoding)
136
- frame << data
137
- frame.force_encoding('binary')
138
- frame
139
- end
140
- end
141
-
142
- module_function
143
- # Sets the defalt Websockt auto-ping interval.
144
- #
145
- # This method accepts one value, which should be either a number in seconds or `false`.
146
- #
147
- # The default ping interval is 45 seconds.
148
- #
149
- # It's possible to set the ping interval to false, thereby disabling auto-pinging.
150
- def ping_interval=(val)
151
- WSResponse.ping_interval = val
152
- end
153
- # Returns the defalt Websockt auto-ping interval.
154
- #
155
- # Plezi will automatically send a ping frame to keep websocket connections open.
156
- # This auto-pinging can be disabled by setting the `ping_interval` to false.
157
- def ping_interval
158
- WSResponse.ping_interval
159
- end
160
-
161
- end