plezi 0.7.7 → 0.8.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +26 -0
- data/README.md +3 -3
- data/lib/plezi.rb +35 -36
- data/lib/plezi/common/cache.rb +94 -0
- data/lib/plezi/{base → common}/dsl.rb +87 -68
- data/lib/plezi/{base → common}/logging.rb +15 -15
- data/lib/plezi/eventmachine/connection.rb +192 -0
- data/lib/plezi/eventmachine/em.rb +95 -0
- data/lib/plezi/eventmachine/io.rb +265 -0
- data/lib/plezi/eventmachine/protocol.rb +54 -0
- data/lib/plezi/eventmachine/queue.rb +51 -0
- data/lib/plezi/eventmachine/ssl_connection.rb +138 -0
- data/lib/plezi/eventmachine/timers.rb +117 -0
- data/lib/plezi/eventmachine/workers.rb +35 -0
- data/lib/plezi/handlers/http_host.rb +4 -4
- data/lib/plezi/handlers/magic_helpers.rb +1 -21
- data/lib/plezi/handlers/route.rb +3 -3
- data/lib/plezi/server/{helpers/http.rb → http.rb} +1 -57
- data/lib/plezi/server/{protocols/http_protocol.rb → http_protocol.rb} +18 -35
- data/lib/plezi/server/{protocols/http_request.rb → http_request.rb} +1 -1
- data/lib/plezi/server/{protocols/http_response.rb → http_response.rb} +45 -22
- data/lib/plezi/server/{helpers/mime_types.rb → mime_types.rb} +0 -0
- data/lib/plezi/server/{protocols/websocket.rb → websocket.rb} +34 -37
- data/lib/plezi/server/{protocols/ws_response.rb → ws_response.rb} +37 -19
- data/lib/plezi/version.rb +1 -1
- data/plezi.gemspec +3 -3
- data/resources/config.ru +9 -11
- metadata +27 -30
- data/lib/plezi/base/cache.rb +0 -89
- data/lib/plezi/base/connections.rb +0 -33
- data/lib/plezi/base/engine.rb +0 -77
- data/lib/plezi/base/events.rb +0 -93
- data/lib/plezi/base/io_reactor.rb +0 -62
- data/lib/plezi/base/rack_app.rb +0 -89
- data/lib/plezi/base/services.rb +0 -57
- data/lib/plezi/base/timers.rb +0 -71
- data/lib/plezi/server/README.md +0 -33
- data/lib/plezi/server/services/basic_service.rb +0 -224
- data/lib/plezi/server/services/no_service.rb +0 -196
- data/lib/plezi/server/services/ssl_service.rb +0 -193
data/lib/plezi/handlers/route.rb
CHANGED
@@ -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.
|
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
|
-
|
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
|
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
|
31
|
+
def on_message
|
38
32
|
# parse the request
|
39
|
-
|
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.";
|
43
|
-
#
|
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 ||=
|
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] || (
|
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] || (
|
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
|
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.
|
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
|
225
|
-
|
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
|
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:
|
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,
|
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
|
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
|
-
#
|
175
|
-
def
|
176
|
-
@headers['content-length'] ||= body[0].bytesize if !headers_sent? && body.is_a?(Array) && body.length == 1
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
#
|
183
|
-
|
184
|
-
|
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
|
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
|
-
|
215
|
-
|
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"
|
File without changes
|
@@ -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
|
-
|
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
|
-
|
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
|
29
|
-
|
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
|
44
|
-
#
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
Plezi.
|
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
|
49
|
+
def on_message
|
54
50
|
# parse the request
|
55
|
-
|
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 /
|
61
|
-
def on_disconnect
|
62
|
-
Plezi.
|
63
|
-
|
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
|
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
|
-
|
99
|
-
|
100
|
-
Plezi.
|
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 @
|
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( @
|
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.
|
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
|
194
|
+
# GET /nickname HTTP/1.1
|
198
195
|
# Upgrade: websocket
|
199
196
|
# Connection: Upgrade
|
200
|
-
# Host: localhost:
|
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
|