plezi 0.9.2 → 0.10.1

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