plezi 0.7.7 → 0.8.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +26 -0
  3. data/README.md +3 -3
  4. data/lib/plezi.rb +35 -36
  5. data/lib/plezi/common/cache.rb +94 -0
  6. data/lib/plezi/{base → common}/dsl.rb +87 -68
  7. data/lib/plezi/{base → common}/logging.rb +15 -15
  8. data/lib/plezi/eventmachine/connection.rb +192 -0
  9. data/lib/plezi/eventmachine/em.rb +95 -0
  10. data/lib/plezi/eventmachine/io.rb +265 -0
  11. data/lib/plezi/eventmachine/protocol.rb +54 -0
  12. data/lib/plezi/eventmachine/queue.rb +51 -0
  13. data/lib/plezi/eventmachine/ssl_connection.rb +138 -0
  14. data/lib/plezi/eventmachine/timers.rb +117 -0
  15. data/lib/plezi/eventmachine/workers.rb +35 -0
  16. data/lib/plezi/handlers/http_host.rb +4 -4
  17. data/lib/plezi/handlers/magic_helpers.rb +1 -21
  18. data/lib/plezi/handlers/route.rb +3 -3
  19. data/lib/plezi/server/{helpers/http.rb → http.rb} +1 -57
  20. data/lib/plezi/server/{protocols/http_protocol.rb → http_protocol.rb} +18 -35
  21. data/lib/plezi/server/{protocols/http_request.rb → http_request.rb} +1 -1
  22. data/lib/plezi/server/{protocols/http_response.rb → http_response.rb} +45 -22
  23. data/lib/plezi/server/{helpers/mime_types.rb → mime_types.rb} +0 -0
  24. data/lib/plezi/server/{protocols/websocket.rb → websocket.rb} +34 -37
  25. data/lib/plezi/server/{protocols/ws_response.rb → ws_response.rb} +37 -19
  26. data/lib/plezi/version.rb +1 -1
  27. data/plezi.gemspec +3 -3
  28. data/resources/config.ru +9 -11
  29. metadata +27 -30
  30. data/lib/plezi/base/cache.rb +0 -89
  31. data/lib/plezi/base/connections.rb +0 -33
  32. data/lib/plezi/base/engine.rb +0 -77
  33. data/lib/plezi/base/events.rb +0 -93
  34. data/lib/plezi/base/io_reactor.rb +0 -62
  35. data/lib/plezi/base/rack_app.rb +0 -89
  36. data/lib/plezi/base/services.rb +0 -57
  37. data/lib/plezi/base/timers.rb +0 -71
  38. data/lib/plezi/server/README.md +0 -33
  39. data/lib/plezi/server/services/basic_service.rb +0 -224
  40. data/lib/plezi/server/services/no_service.rb +0 -196
  41. data/lib/plezi/server/services/ssl_service.rb +0 -193
@@ -218,7 +218,7 @@ module Plezi
218
218
  # flash:: an amazing Hash object that sets temporary cookies for one request only - greate for saving data between redirect calls.
219
219
  #
220
220
  def self.make_controller_magic(controller)
221
- new_class_name = "Plezi__#{controller.name.gsub /[\:\-]/, '_'}"
221
+ new_class_name = "Plezi__#{controller.name.gsub /[\:\-\#\<\>\{\}\(\)\s]/, '_'}"
222
222
  return Module.const_get new_class_name if Module.const_defined? new_class_name
223
223
  # controller.include Plezi::ControllerMagic
224
224
  controller.instance_eval { include Plezi::ControllerMagic }
@@ -252,11 +252,11 @@ module Plezi
252
252
  # make sure this is a websocket controller
253
253
  return false unless self.class.public_instance_methods.include?(:on_message)
254
254
  # call the controller's original method, if exists, and check connection.
255
- return false if (defined?(super) && !super)
255
+ return false if (defined?(super) && !super)
256
256
  # finish if the response was sent
257
257
  return true if response.headers_sent?
258
258
  # complete handshake
259
- return false unless WSProtocol.new( request.service, request.service.parameters).http_handshake request, response, self
259
+ return false unless WSProtocol.new( request.service, request.service.params).http_handshake request, response, self
260
260
  # set up controller as WebSocket handler
261
261
  @response = WSResponse.new request
262
262
  # create the redis connection (in case this in the first instance of this class)
@@ -7,62 +7,6 @@ module Plezi
7
7
  # includes general helper methods for HTTP protocol and related (url encoding etc')
8
8
  module HTTP
9
9
  module_function
10
- # decode html form data stream
11
- # def decode_form_data encoded
12
- # scanner = StringScanner.new encoded.gsub('+', '%20')
13
- # decoded = ''
14
- # until scanner.eos? do
15
- # decoded << scanner.scan(/[^%]+/)
16
- # if scanner.scan(/\%[0-9a-fA-F][0-9a-fA-F]/)
17
- # decoded << scanner.matched[1,2].to_i(16).chr
18
- # elsif !scanner.eos?
19
- # decoded << scanner.scan(/./)
20
- # end
21
- # end
22
- # decoded
23
- # end
24
- # # encode html form data stream
25
- # def encode_form_data exposed
26
- # scanner = StringScanner.new exposed
27
- # encoded = ''
28
-
29
- # # HTML form encoding
30
- # until scanner.eos? do
31
- # encoded << scanner.scan(/[a-zA-Z0-9\*\.\_\-]+/)
32
- # encoded << "%#{scanner.matched.ord <= 16 ? "0" : ""} #{ scanner.matched.ord.to_s(16) }" if scanner.scan(/./)
33
- # end
34
-
35
- # # HTTP encoding
36
- # # until scanner.eos? do
37
- # # encoded << scanner.scan(/[^\:\/\?\#\[\]\@\!\$\&\'\(\)\*\+\,\;\=]+/)
38
- # # encoded << "%#{scanner.matched.ord <= 16 ? "0" : ""} #{ scanner.matched.ord.to_s(16) }" if scanner.scan(/./)
39
- # # end
40
- # encoded
41
- # end
42
- # # decode HTTP URI data stream
43
- # def decode_uri encoded
44
- # scanner = StringScanner.new encoded.gsub('+', '%20') #? is this true here?
45
- # decoded = ''
46
- # until scanner.eos? do
47
- # decoded << scanner.scan(/[^%]+/)
48
- # if scanner.scan(/\%[0-9a-fA-F][0-9a-fA-F]/)
49
- # decoded << scanner.matched[1,2].to_i(16).chr
50
- # elsif !scanner.eos?
51
- # decoded << scanner.scan(/./)
52
- # end
53
- # end
54
- # decoded
55
- # end
56
- # # encode HTTP URI data stream
57
- # def encode_uri_data exposed
58
- # scanner = StringScanner.new exposed
59
- # encoded = ''
60
- # until scanner.eos? do
61
- # encoded << scanner.scan(/[^\:\/\?\#\[\]\@\!\$\&\'\(\)\*\+\,\;\=]+/)
62
- # encoded << "%#{scanner.matched.ord <= 16 ? "0" : ""} #{ scanner.matched.ord.to_s(16) }" if scanner.scan(/.|[\s]/)
63
- # end
64
- # encoded
65
- # end
66
10
 
67
11
  # Based on the WEBRick source code, escapes &, ", > and < in a String object
68
12
  def escape(string)
@@ -170,7 +114,7 @@ module Plezi
170
114
  # Changes String to a Ruby Object, if it's a special string
171
115
  def rubyfy!(string)
172
116
  return false unless string
173
- make_utf8! string
117
+ # make_utf8! string
174
118
  if string == 'true'
175
119
  string = true
176
120
  elsif string == 'false'
@@ -4,64 +4,47 @@ module Plezi
4
4
  #
5
5
  #
6
6
  # to do: implemet logging, support body types: multipart (non-ASCII form data / uploaded files), json & xml
7
- class HTTPProtocol
7
+ class HTTPProtocol < EventMachine::Protocol
8
8
 
9
9
  HTTP_METHODS = %w{GET HEAD POST PUT DELETE TRACE OPTIONS}
10
10
  HTTP_METHODS_REGEXP = /^#{HTTP_METHODS.join('|')}/
11
+ HTTP_FIRE_REQUEST = Proc.new {|handler, request| handler.on_request request}
11
12
 
12
- attr_accessor :service
13
-
14
- def initialize service, params
15
- @service = service
13
+ def initialize connection, params
14
+ super
16
15
  @parser_stage = 0
17
16
  @parser_data = {}
18
17
  @parser_body = ''
19
18
  @parser_chunk = ''
20
19
  @parser_length = 0
21
- @locker = Mutex.new
22
20
  @@rack_dictionary ||= {'HOST'.freeze => :host_name, 'REQUEST_METHOD'.freeze => :method,
23
21
  'PATH_INFO'.freeze => :path, 'QUERY_STRING'.freeze => :query,
24
22
  'SERVER_NAME'.freeze => :host_name, 'SERVER_PORT'.freeze => :port,
25
23
  'rack.url_scheme'.freeze => :requested_protocol}
26
24
  end
27
25
 
28
- # called when connection is initialized.
29
- def on_connect service
30
- end
31
-
32
26
  # called when data is recieved.
33
27
  #
34
- # this method is called within a lock on the service (Mutex) - craeful from double locking.
28
+ # this method is called within a lock on the connection (Mutex) - craeful from double locking.
35
29
  #
36
30
  # typically returns an Array with any data not yet processed (to be returned to the in-que)... but here it always processes (or discards) the data.
37
- def on_message(service)
31
+ def on_message
38
32
  # parse the request
39
- @locker.synchronize { parse_message }
33
+ parse_message
40
34
  if (@parser_stage == 1) && @parser_data[:version] >= 1.1
41
35
  # send 100 continue message????? doesn't work! both Crome and Safari go crazy if this is sent after the request was sent (but before all the packets were recieved... msgs over 1 Mb).
42
- # Plezi.push_event Proc.new { Plezi.info "sending continue signal."; service.send_nonblock "100 Continue\r\n\r\n" }
43
- # service.send_unsafe_interrupt "100 Continue\r\n\r\n" # causes double lock on service
36
+ # Plezi.push_event Proc.new { Plezi.info "sending continue signal."; connection.send_nonblock "100 Continue\r\n\r\n" }
37
+ # connection.send_unsafe_interrupt "100 Continue\r\n\r\n" # causes double lock on connection
44
38
  end
45
39
  true
46
40
  end
47
41
 
48
- # # called when a disconnect is fired
49
- # # (socket was disconnected / service should be disconnected / shutdown / socket error)
50
- def on_disconnect service
51
- end
52
-
53
- # called when an exception was raised
54
- # (socket was disconnected / service should be disconnected / shutdown / socket error)
55
- def on_exception service, e
56
- Plezi.error e
57
- end
58
-
59
42
 
60
43
  # Protocol specific helper methods.
61
44
 
62
45
  # parses incoming data
63
46
  def parse_message data = nil
64
- data ||= service.read.to_s.lines.to_a
47
+ data ||= connection.read.to_s.lines.to_a
65
48
  # require 'pry'; binding.pry
66
49
  if @parser_stage == 0
67
50
  return false unless parse_method data
@@ -85,7 +68,7 @@ module Plezi
85
68
  @parser_data[:query] = ''
86
69
  @parser_data[:original_path] = ''
87
70
  @parser_data[:path] = ''
88
- if defined? Rack
71
+ if defined? ::Rack
89
72
  @parser_data['rack.version'] = Rack::VERSION
90
73
  @parser_data['rack.multithread'] = true
91
74
  @parser_data['rack.multiprocess'] = false
@@ -167,7 +150,7 @@ module Plezi
167
150
  def complete_request
168
151
  #finalize params and query properties
169
152
  m = @parser_data[:query].match /(([a-z0-9A-Z]+):\/\/)?(([^\/\:]+))?(:([0-9]+))?([^\?\#]*)(\?([^\#]*))?/
170
- @parser_data[:requested_protocol] = m[1] || (service.ssl? ? 'https' : 'http')
153
+ @parser_data[:requested_protocol] = m[1] || (connection.ssl? ? 'https' : 'http')
171
154
  @parser_data[:host_name] = m[4] || (@parser_data['host'] ? @parser_data['host'].match(/^[^:]*/).to_s : nil)
172
155
  @parser_data[:port] = m[6] || (@parser_data['host'] ? @parser_data['host'].match(/:([0-9]*)/).to_a[1] : nil)
173
156
  @parser_data[:original_path] = HTTP.decode(m[7], :uri) || '/'
@@ -190,12 +173,12 @@ module Plezi
190
173
  HTTP.make_utf8! @parser_data[:host_name] if @parser_data[:host_name]
191
174
  HTTP.make_utf8! @parser_data[:query]
192
175
 
193
- @parser_data[:client_ip] = @parser_data['x-forwarded-for'].to_s.split(/,[\s]?/)[0] || (service.socket.remote_address.ip_address) rescue 'unknown IP'
176
+ @parser_data[:client_ip] = @parser_data['x-forwarded-for'].to_s.split(/,[\s]?/)[0] || (connection.socket.remote_address.ip_address) rescue 'unknown IP'
194
177
 
195
178
  @@rack_dictionary.each {|k,v| @parser_data[k] = @parser_data[v]}
196
179
 
197
180
  #create request
198
- request = HTTPRequest.new service
181
+ request = HTTPRequest.new connection
199
182
  request.update @parser_data
200
183
 
201
184
  #clear current state
@@ -210,7 +193,7 @@ module Plezi
210
193
  when 'TRACE'
211
194
  return true
212
195
  when 'OPTIONS'
213
- Plezi.push_event Proc.new do
196
+ Plezi.run_async do
214
197
  response = HTTPResponse.new request
215
198
  response[:Allow] = 'GET,HEAD,POST,PUT,DELETE,OPTIONS'
216
199
  response['access-control-allow-origin'] = '*'
@@ -221,10 +204,10 @@ module Plezi
221
204
  end
222
205
 
223
206
  #pass it to the handler or decler error.
224
- if service && service.handler
225
- Plezi.callback service.handler, :on_request, request
207
+ if connection && connection.handler
208
+ EventMachine.queue [connection.handler, request], HTTP_FIRE_REQUEST
226
209
  else
227
- Plezi.error 'No Handler for this HTTP service.'
210
+ Plezi.error 'No Handler for this HTTP connection.'
228
211
  end
229
212
  end
230
213
 
@@ -132,7 +132,7 @@ end
132
132
  ## example requests
133
133
 
134
134
  # GET / HTTP/1.1
135
- # Host: localhost:2000
135
+ # Host: localhost:3000
136
136
  # Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
137
137
  # Cookie: user_token=2INa32_vDgx8Aa1qe43oILELpSdIe9xwmT8GTWjkS-w
138
138
  # 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
@@ -33,11 +33,12 @@ module Plezi
33
33
  #
34
34
  # use, at the very least `HTTPResponse.new request`
35
35
  def initialize request, status = 200, headers = {}, body = []
36
- @request, @status, @headers, @body, @service = request, status, headers, body, (defined?(PLEZI_ON_RACK) ? false : request.service)
36
+ @request, @status, @headers, @body, @service = request, status, headers, body, request[:plezi_service]
37
37
  @http_version = 'HTTP/1.1' # request.version
38
38
  @bytes_sent = 0
39
39
  @finished = @streaming = false
40
40
  @cookies = {}
41
+ @chunked = false
41
42
  # propegate flash object
42
43
  @flash = Hash.new do |hs,k|
43
44
  hs["plezi_flash_#{k.to_s}".to_sym] if hs.has_key? "plezi_flash_#{k.to_s}".to_sym
@@ -70,7 +71,7 @@ module Plezi
70
71
  # If HTTP streaming is set, you will need to manually call `response.finish`
71
72
  # of the connection will not close properly.
72
73
  def start_http_streaming
73
- @streaming = true
74
+ @streaming = @chunked = true
74
75
  end
75
76
 
76
77
  # pushes data to the body of the response. this is the preferred way to add data to the response.
@@ -155,7 +156,7 @@ module Plezi
155
156
  body << str if str && body.is_a?(Array)
156
157
  send_headers
157
158
  return if request.head?
158
- if headers['transfer-encoding'] == 'chunked'
159
+ if @chunked
159
160
  body.each do |s|
160
161
  service.send "#{s.bytesize.to_s(16)}\r\n"
161
162
  service.send s
@@ -171,17 +172,30 @@ module Plezi
171
172
  @body.is_a?(Array) ? @body.clear : ( @body = [] )
172
173
  end
173
174
 
174
- # sends the response and flags the response as complete. future data should not be sent. the flag will only be enforced be the Plezi router. your code might attempt sending data (which would probbaly be ignored by the client or raise an exception).
175
- def finish
176
- @headers['content-length'] ||= body[0].bytesize if !headers_sent? && body.is_a?(Array) && body.length == 1
177
- return self if defined?(PLEZI_ON_RACK)
178
- raise 'HTTPResponse SERVICE MISSING: cannot send http response without a service.' unless service
179
- self.send
180
- service.send( (headers['transfer-encoding'] == 'chunked') ? "0\r\n\r\n" : nil)
181
- @finished = true
182
- # service.disconnect unless headers['keep-alive']
183
- # log
184
- Plezi.log_raw "#{request[:client_ip]} [#{Time.now.utc}] \"#{request[:method]} #{request[:original_path]} #{request[:requested_protocol]}\/#{request[:version]}\" #{status} #{bytes_sent.to_s} #{"%0.3f" % ((Time.now - request[:time_recieved])*1000)}ms\n"
175
+ # prep for rack response
176
+ def prep_rack
177
+ @headers['content-length'] ||= body[0].bytesize.to_s if !headers_sent? && body.is_a?(Array) && body.length == 1
178
+ fix_cookie_headers
179
+ end
180
+
181
+
182
+ if defined?(PLEZI_ON_RACK)
183
+ # does nothing.
184
+ def finish
185
+ false
186
+ end
187
+ else
188
+ # sends the response and flags the response as complete. future data should not be sent. the flag will only be enforced be the Plezi router. your code might attempt sending data (which would probbaly be ignored by the client or raise an exception).
189
+ def finish
190
+ @headers['content-length'] ||= body[0].bytesize if !headers_sent? && body.is_a?(Array) && body.length == 1
191
+ self.send
192
+ service.send( (@chunked) ? "0\r\n\r\n" : nil)
193
+ @finished = true
194
+ # service.disconnect unless headers['keep-alive']
195
+ # log
196
+ Plezi.log_raw "#{request[:client_ip]} [#{Time.now.utc}] \"#{request[:method]} #{request[:original_path]} #{request[:requested_protocol]}\/#{request[:version]}\" #{status} #{bytes_sent.to_s} #{"%0.3f" % ((Time.now - request[:time_recieved])*1000)}ms\n"
197
+ end
198
+
185
199
  end
186
200
 
187
201
  # Danger Zone (internally used method, use with care): attempts to finish the response - if it was not flaged as streaming or completed.
@@ -190,12 +204,7 @@ module Plezi
190
204
  end
191
205
 
192
206
  # Danger Zone (internally used method, use with care): fix response's headers before sending them (date, connection and transfer-coding).
193
- def fix_headers
194
- headers['keep-alive'] ||= "timeout=5" unless headers['connection']
195
- headers['connection'] ||= "Keep-Alive"
196
- headers['date'] = Time.now.httpdate
197
- headers['transfer-encoding'] ||= 'chunked' unless headers['content-length']
198
- headers['cache-control'] ||= 'no-cache'
207
+ def fix_cookie_headers
199
208
  # remove old flash cookies
200
209
  request.cookies.keys.each do |k|
201
210
  if k.to_s.start_with? 'plezi_flash_'
@@ -211,8 +220,22 @@ module Plezi
211
220
  # Danger Zone (internally used method, use with care): fix response's headers before sending them (date, connection and transfer-coding).
212
221
  def send_headers
213
222
  return false if @headers.frozen?
214
- fix_headers
215
- service.send "#{@http_version} #{status} #{STATUS_CODES[status] || 'unknown'}\r\n"
223
+ fix_cookie_headers
224
+ headers['cache-control'] ||= 'no-cache'
225
+
226
+ service.send "#{@http_version} #{status} #{STATUS_CODES[status] || 'unknown'}\r\nDate: #{Time.now.httpdate}\r\n"
227
+
228
+ unless headers['connection']
229
+ service.send "Connection: Keep-Alive\r\nKeep-Alive: timeout=5\r\n"
230
+ end
231
+
232
+ if headers['content-length']
233
+ @chunked = false
234
+ else
235
+ @chunked = true
236
+ service.send "Transfer-Encoding: chunked\r\n"
237
+ end
238
+
216
239
  headers.each {|k,v| service.send "#{k.to_s}: #{v}\r\n"}
217
240
  @cookies.each {|k,v| service.send "Set-Cookie: #{k.to_s}=#{v.to_s}\r\n"}
218
241
  service.send "\r\n"
@@ -4,7 +4,7 @@ module Plezi
4
4
  #
5
5
  #
6
6
  # to do: implemet logging, support body types: multipart (non-ASCII form data / uploaded files), json & xml
7
- class WSProtocol
7
+ class WSProtocol < EventMachine::Protocol
8
8
 
9
9
  SUPPORTED_EXTENTIONS = {}
10
10
  # SUPPORTED_EXTENTIONS['x-webkit-deflate-frame'] = Proc.new {|body, params| }
@@ -12,60 +12,53 @@ module Plezi
12
12
 
13
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
14
  def timeout_interval
15
- @timeout_interval
15
+ connection.timeout
16
16
  end
17
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
18
  def timeout_interval= value
19
- @timeout_interval = value
20
- Plezi.callback service, :set_timeout, @timeout_interval
19
+ connection.timeout = value
21
20
  end
22
21
 
23
- # the service (holding the socket) over which this protocol is running.
24
- attr_reader :service
25
22
  # the extentions registered for the websockets connection.
26
23
  attr_reader :extentions
27
24
 
28
- def initialize service, params
29
- @params = params
30
- @service = service
25
+ def initialize connection, params
26
+ super
31
27
  @extentions = []
32
28
  @locker = Mutex.new
33
29
  @parser_stage = 0
34
30
  @parser_data = {}
35
31
  @parser_data[:body] = []
36
32
  @parser_data[:step] = 0
37
- @in_que = []
38
33
  @message = ''
39
- @timeout_interval = 60
40
34
  end
41
35
 
36
+ # a proc object that calls #on_connect for the handler passed.
37
+ ON_CONNECT_PROC = Proc.new {|handler| handler.on_connect}
42
38
  # called when connection is initialized.
43
- def on_connect service
44
- # cancel service timeout? (for now, reset to 60 seconds)
45
- service.timeout = @timeout_interval
46
- # Plezi.callback service, :timeout=, @timeout_interval
47
- Plezi.callback @service.handler, :on_connect if @service.handler.methods.include?(:on_connect)
48
- Plezi.info 'Upgraded HTTP to WebSockets. Logging only errors.'
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 }
49
45
  end
50
46
 
51
47
  # called when data is recieved
52
48
  # returns an Array with any data not yet processed (to be returned to the in-que).
53
- def on_message(service)
49
+ def on_message
54
50
  # parse the request
55
- return @locker.synchronize {extract_message service.read.bytes}
51
+ extract_message connection.read.to_s.bytes
56
52
  true
57
53
  end
58
54
 
55
+ # a proc object that calls #on_disconnect for the handler passed.
56
+ ON_DISCONNECT_PROC = Proc.new {|handler| handler.on_disconnect}
59
57
  # called when a disconnect is fired
60
- # (socket was disconnected / service should be disconnected / shutdown / socket error)
61
- def on_disconnect service
62
- Plezi.callback @service.handler, :on_disconnect if @service.handler.methods.include?(:on_disconnect)
63
- end
64
-
65
- # called when an exception was raised
66
- # (socket was disconnected / service should be disconnected / shutdown / socket error)
67
- def on_exception service, e
68
- Plezi.error e
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)
69
62
  end
70
63
 
71
64
  ########
@@ -77,11 +70,12 @@ module Plezi
77
70
  # should consider adopting the websocket gem for handshake and framing:
78
71
  # https://github.com/imanel/websocket-ruby
79
72
  # http://www.rubydoc.info/github/imanel/websocket-ruby
80
- return request.service.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' &&
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' &&
81
74
  request['sec-websocket-key'] &&
82
75
  request['connection'].to_s.downcase == 'upgrade' &&
83
76
  # (request['sec-websocket-extensions'].split(/[\s]*[,][\s]*/).reject {|ex| ex == '' || SUPPORTED_EXTENTIONS[ex.split(/[\s]*;[\s]*/)[0]] } ).empty? &&
84
77
  (request['sec-websocket-version'].to_s.downcase.split(/[, ]/).map {|s| s.strip} .include?( '13' ))
78
+ @request = request
85
79
  response.status = 101
86
80
  response['upgrade'] = 'websocket'
87
81
  response['content-length'] = '0'
@@ -95,9 +89,9 @@ module Plezi
95
89
  response['Sec-WebSocket-Accept'] = Digest::SHA1.base64digest(request['sec-websocket-key'] + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')
96
90
  response.finish
97
91
  @extentions.freeze
98
- response.service.protocol = self
99
- response.service.handler = handler
100
- Plezi.callback self, :on_connect, response.service
92
+ connection.protocol = self
93
+ connection.handler = handler
94
+ Plezi::EventMachine.queue [self], ON_CONNECT_PROC
101
95
  return true
102
96
  end
103
97
 
@@ -160,6 +154,9 @@ module Plezi
160
154
  bytes.pop ^ (merge_bytes(*bytes) << 8)
161
155
  end
162
156
 
157
+ # The proc queued whenever a frame is complete.
158
+ COMPLETE_FRAME_PROC = Proc.new {|handler, message| handler.on_message message}
159
+
163
160
  # handles the completed frame and sends a message to the handler once all the data has arrived.
164
161
  def complete_frame
165
162
  @extentions.each {|ex| SUPPORTED_EXTENTIONS[ex[0]][1].call(@parser_data[:body], ex[1..-1]) if SUPPORTED_EXTENTIONS[ex[0]]}
@@ -167,18 +164,18 @@ module Plezi
167
164
  case @parser_data[:op_code]
168
165
  when 9, 10
169
166
  # handle @parser_data[:op_code] == 9 (ping) / @parser_data[:op_code] == 10 (pong)
170
- Plezi.callback @service, :send_nonblock, WSResponse.frame_data(@parser_data[:body].pack('C*'), 10)
167
+ Plezi.callback @connection, :send_nonblock, WSResponse.frame_data(@parser_data[:body].pack('C*'), 10) # unless @parser_data[:op_code] == 10
171
168
  @parser_op_code = nil if @parser_op_code == 9 || @parser_op_code == 10
172
169
  when 8
173
170
  # handle @parser_data[:op_code] == 8 (close)
174
- Plezi.callback( @service, :send_nonblock, WSResponse.frame_data('', 8) ) { @service.disconnect }
171
+ Plezi.callback( @connection, :send_nonblock, WSResponse.frame_data('', 8) ) { @connection.disconnect }
175
172
  @parser_op_code = nil if @parser_op_code == 8
176
173
  else
177
174
  @message << @parser_data[:body].pack('C*')
178
175
  # handle @parser_data[:op_code] == 0 / fin == false (continue a frame that hasn't ended yet)
179
176
  if @parser_data[:fin]
180
177
  HTTP.make_utf8! @message if @parser_op_code == 1
181
- Plezi.callback @service.handler, :on_message, @message
178
+ Plezi::EventMachine.queue [@connection.handler, @message], COMPLETE_FRAME_PROC
182
179
  @message = ''
183
180
  @parser_op_code = nil
184
181
  end
@@ -194,10 +191,10 @@ end
194
191
  ######
195
192
  ## example requests
196
193
 
197
- # GET /?encoding=text HTTP/1.1
194
+ # GET /nickname HTTP/1.1
198
195
  # Upgrade: websocket
199
196
  # Connection: Upgrade
200
- # Host: localhost:3001
197
+ # Host: localhost:3000
201
198
  # Origin: https://www.websocket.org
202
199
  # Cookie: test=my%20cookies; user_token=2INa32_vDgx8Aa1qe43oILELpSdIe9xwmT8GTWjkS-w
203
200
  # Pragma: no-cache