plezi 0.7.7 → 0.8.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.
- 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
@@ -33,28 +33,28 @@ module Plezi
|
|
33
33
|
end
|
34
34
|
|
35
35
|
# logs info
|
36
|
-
def log
|
37
|
-
@logger.info
|
38
|
-
@copy_to_stdout.info
|
36
|
+
def log data
|
37
|
+
@logger.info data
|
38
|
+
@copy_to_stdout.info data if @copy_to_stdout
|
39
39
|
end
|
40
40
|
# logs info
|
41
|
-
def info
|
42
|
-
@logger.info
|
43
|
-
@copy_to_stdout.info
|
41
|
+
def info data
|
42
|
+
@logger.info data
|
43
|
+
@copy_to_stdout.info data if @copy_to_stdout
|
44
44
|
end
|
45
45
|
# logs warning
|
46
|
-
def warn
|
47
|
-
@logger.warn
|
48
|
-
@copy_to_stdout.warn
|
46
|
+
def warn data
|
47
|
+
@logger.warn data
|
48
|
+
@copy_to_stdout.warn data if @copy_to_stdout
|
49
49
|
end
|
50
50
|
# logs errors
|
51
|
-
def error
|
52
|
-
@logger.error
|
53
|
-
@copy_to_stdout.error
|
51
|
+
def error data
|
52
|
+
@logger.error data
|
53
|
+
@copy_to_stdout.error data if @copy_to_stdout
|
54
54
|
end
|
55
55
|
# logs a fatal error
|
56
|
-
def fatal
|
57
|
-
@logger.fatal
|
58
|
-
@copy_to_stdout.fatal
|
56
|
+
def fatal data
|
57
|
+
@logger.fatal data
|
58
|
+
@copy_to_stdout.fatal data if @copy_to_stdout
|
59
59
|
end
|
60
60
|
end
|
@@ -0,0 +1,192 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
|
4
|
+
|
5
|
+
module Plezi
|
6
|
+
|
7
|
+
module EventMachine
|
8
|
+
|
9
|
+
class Connection
|
10
|
+
attr_reader :socket, :params, :active_time, :out_que, :locker
|
11
|
+
attr_accessor :protocol, :handler, :timeout
|
12
|
+
|
13
|
+
# initializes the connection and it's settings.
|
14
|
+
def initialize socket, params
|
15
|
+
@socket, @params, @handler = socket, params, params[:handler]
|
16
|
+
# socket.setsockopt Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, "\n\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" #== [10 sec 0 usec].pack '1_2'
|
17
|
+
@out_que, @locker = [], Mutex.new
|
18
|
+
@protocol = params[:protocol].is_a?(Class) ? (params[:protocol].new self, params) : params[:protocol]
|
19
|
+
@protocol.on_connect if @protocol.is_a?(Protocol)
|
20
|
+
touch
|
21
|
+
@timeout ||= 5
|
22
|
+
end
|
23
|
+
|
24
|
+
# used by the EM when the connection should handle data.
|
25
|
+
def call
|
26
|
+
# don't let competing threads do the same job.
|
27
|
+
return false if @locker.locked?
|
28
|
+
begin
|
29
|
+
@locker.synchronize do
|
30
|
+
return disconnect if disconnected?
|
31
|
+
protocol.on_message
|
32
|
+
end
|
33
|
+
|
34
|
+
rescue Exception => e
|
35
|
+
PL.error e
|
36
|
+
return disconnect
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def clear?
|
41
|
+
return false unless timedout? || disconnected?
|
42
|
+
disconnect
|
43
|
+
true
|
44
|
+
end
|
45
|
+
|
46
|
+
# checks if a connection timed out
|
47
|
+
def timedout?
|
48
|
+
Time.now - @active_time > @timeout.to_i
|
49
|
+
end
|
50
|
+
|
51
|
+
# resets the timer for the connection timeout
|
52
|
+
def touch
|
53
|
+
@active_time = Time.now
|
54
|
+
end
|
55
|
+
|
56
|
+
# returns an IO-like object used for reading/writing (unlike the original IO object, this can be an SSL layer or any other wrapper object).
|
57
|
+
def io
|
58
|
+
@socket
|
59
|
+
end
|
60
|
+
|
61
|
+
# sends data immidiately - forcing the data to be sent, flushing any pending messages in the que
|
62
|
+
def send data = nil
|
63
|
+
return false unless @out_que.any? || data
|
64
|
+
@locker.synchronize do
|
65
|
+
unless @out_que.empty?
|
66
|
+
@out_que.each { |d| _send d rescue disconnect }
|
67
|
+
@out_que.clear
|
68
|
+
end
|
69
|
+
(_send data rescue disconnect) if data
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# the non-blocking proc used for send_nonblock
|
74
|
+
SEND_COMPLETE_PROC = Proc.new {|c| c.send }
|
75
|
+
|
76
|
+
# sends data without waiting - data might be sent in a different order then intended.
|
77
|
+
def send_nonblock data
|
78
|
+
touch
|
79
|
+
@locker.synchronize {@out_que << data}
|
80
|
+
EventMachine.queue [self], SEND_COMPLETE_PROC
|
81
|
+
end
|
82
|
+
|
83
|
+
# adds data to the out buffer - but doesn't send the data until a send event is called.
|
84
|
+
def << data
|
85
|
+
touch
|
86
|
+
@locker.synchronize {@out_que << data}
|
87
|
+
end
|
88
|
+
|
89
|
+
# makes sure any data in the que is send and calls `flush` on the socket, to make sure the buffer is sent.
|
90
|
+
def flush
|
91
|
+
send
|
92
|
+
io.flush
|
93
|
+
end
|
94
|
+
|
95
|
+
# the proc used to remove the connection from the IO stack.
|
96
|
+
REMOVE_CONNECTION_PROC = Proc.new {|old_io| EventMachine.remove_io old_io }
|
97
|
+
# called once a socket is disconnected or needs to be disconnected.
|
98
|
+
def on_disconnect
|
99
|
+
EventMachine.queue [@socket], REMOVE_CONNECTION_PROC
|
100
|
+
@locker.synchronize do
|
101
|
+
@out_que.each { |d| _send d rescue true}
|
102
|
+
@out_que.clear
|
103
|
+
io.flush rescue true
|
104
|
+
io.close rescue true
|
105
|
+
end
|
106
|
+
EventMachine.queue [], protocol.method(:on_disconnect) if protocol && !protocol.is_a?(Class)
|
107
|
+
end
|
108
|
+
|
109
|
+
# status markers
|
110
|
+
|
111
|
+
# closes the connection
|
112
|
+
def close
|
113
|
+
@locker.synchronize do
|
114
|
+
io.flush rescue true
|
115
|
+
io.close rescue true
|
116
|
+
end
|
117
|
+
end
|
118
|
+
# returns true if the service is disconnected
|
119
|
+
def disconnected?
|
120
|
+
(@socket.closed? || socket.stat.mode == 0140222) rescue true # if mode is read only, it's the same as closed.
|
121
|
+
end
|
122
|
+
# the async disconnect proc
|
123
|
+
FIRE_DISCONNECT_PROC = Proc.new {|handler| handler.on_disconnect }
|
124
|
+
# disconects the service.
|
125
|
+
def disconnect
|
126
|
+
@out_que.clear
|
127
|
+
EventMachine.queue [self], FIRE_DISCONNECT_PROC
|
128
|
+
end
|
129
|
+
# returns true if the socket has content to be read.
|
130
|
+
def has_incoming_data?
|
131
|
+
(@socket.stat.size > 0) rescue false
|
132
|
+
end
|
133
|
+
|
134
|
+
|
135
|
+
# identification markers
|
136
|
+
|
137
|
+
#returns the service type - set to normal
|
138
|
+
def service_type
|
139
|
+
'normal'
|
140
|
+
end
|
141
|
+
#returns true if the service is encrypted using the OpenSSL library.
|
142
|
+
def ssl?
|
143
|
+
false
|
144
|
+
end
|
145
|
+
|
146
|
+
#################
|
147
|
+
# overide the followind methods for any child class.
|
148
|
+
|
149
|
+
# this is a public method and it should be used by child classes to implement each
|
150
|
+
# read(_nonblock) action. accepts one argument ::size for an optional buffer size to be read.
|
151
|
+
def read size = 1048576
|
152
|
+
begin
|
153
|
+
data = @socket.recv_nonblock( size )
|
154
|
+
return nil if data.to_s.empty?
|
155
|
+
touch
|
156
|
+
data
|
157
|
+
rescue Exception => e
|
158
|
+
|
159
|
+
end
|
160
|
+
end
|
161
|
+
# # this is a public method and it should be used by child classes to implement each
|
162
|
+
# # read(_nonblock) action. accepts one argument ::size for an optional buffer size to be read.
|
163
|
+
# def read_line
|
164
|
+
# data = @line_data ||= ''
|
165
|
+
# begin
|
166
|
+
# data << @socket.recv_nonblock( 1 ).to_s until data[-1] == "\n"
|
167
|
+
# @line_data = ''
|
168
|
+
# return data
|
169
|
+
# rescue => e
|
170
|
+
# return false
|
171
|
+
# ensure
|
172
|
+
# touch
|
173
|
+
# end
|
174
|
+
# end
|
175
|
+
|
176
|
+
protected
|
177
|
+
|
178
|
+
# this is a protected method, it should be used by child classes to implement each
|
179
|
+
# send action.
|
180
|
+
def _send data
|
181
|
+
@active_time += 7200
|
182
|
+
len = data.bytesize
|
183
|
+
act = @socket.send data, 0
|
184
|
+
while len > act
|
185
|
+
act += @socket.send data.byteslice(act..-1) , 0
|
186
|
+
end
|
187
|
+
touch
|
188
|
+
end
|
189
|
+
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
module Plezi
|
2
|
+
|
3
|
+
# This module holds the events, timers and IO logic and workflow (asynchronous workflow).
|
4
|
+
#
|
5
|
+
# Timed events are approximated and their exact time of execution is dependant on the workload and continues uptime of the process (timed events AREN'T persistant)
|
6
|
+
module EventMachine
|
7
|
+
|
8
|
+
module_function
|
9
|
+
|
10
|
+
# sets the amount of worker threads cycling the event machine and starts (or re-starts) the event machine.
|
11
|
+
def start count=12
|
12
|
+
@workers ||= []
|
13
|
+
@workers_lock ||= Mutex.new
|
14
|
+
stop unless @workers.count <= count
|
15
|
+
(count - @workers.count).times {@workers << Worker.new}
|
16
|
+
end
|
17
|
+
|
18
|
+
# runs through all existing events and one idle cycle
|
19
|
+
def run wait = 0.1
|
20
|
+
begin
|
21
|
+
fire_timers
|
22
|
+
do_job while do_job
|
23
|
+
# replace with io
|
24
|
+
review_io || sleep(wait)
|
25
|
+
rescue => e
|
26
|
+
Plezi.error e
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def stop_and_wait
|
31
|
+
@workers_lock.synchronize { @workers.each {|w| w.stop }; @workers.each {|w| w.join }; @workers.clear; Worker.reset_wait}
|
32
|
+
end
|
33
|
+
|
34
|
+
def stop
|
35
|
+
@workers_lock.synchronize { @workers.each {|w| w.stop } ; @workers.clear; Worker.reset_wait}
|
36
|
+
end
|
37
|
+
|
38
|
+
def running?
|
39
|
+
@workers && @workers.any?
|
40
|
+
end
|
41
|
+
def count_living_workers
|
42
|
+
(@workers_lock.synchronize { @workers.select {|w| w.alive? } } ).count
|
43
|
+
end
|
44
|
+
def workers_status
|
45
|
+
@workers_lock.synchronize { @workers.map {|w| w.status } }
|
46
|
+
end
|
47
|
+
|
48
|
+
SHUTDOWN_CALLBACKS = []
|
49
|
+
# runs the shutdown queue
|
50
|
+
def shutdown
|
51
|
+
stop_and_wait rescue false
|
52
|
+
old_timeout = io_timeout
|
53
|
+
io_timeout = 0.001
|
54
|
+
run
|
55
|
+
io_timeout = old_timeout
|
56
|
+
do_job while do_job
|
57
|
+
stop_connections
|
58
|
+
do_job while do_job
|
59
|
+
QUEUE_LOCKER.synchronize do
|
60
|
+
SHUTDOWN_CALLBACKS.each { |s_job| s_job[0].call(*s_job[1]) }
|
61
|
+
SHUTDOWN_CALLBACKS.clear
|
62
|
+
end
|
63
|
+
true
|
64
|
+
end
|
65
|
+
# Adds a callback to be called once the services were shut down. see: callback for more info.
|
66
|
+
def on_shutdown object=nil, method=nil, *args, &block
|
67
|
+
if block && !object && !method
|
68
|
+
QUEUE_LOCKER.synchronize {SHUTDOWN_CALLBACKS << [block, args]}
|
69
|
+
elsif block
|
70
|
+
QUEUE_LOCKER.synchronize {SHUTDOWN_CALLBACKS << [(Proc.new {|*a| block.call(object.method(method).call(*a))} ), args]}
|
71
|
+
elsif object && method
|
72
|
+
QUEUE_LOCKER.synchronize {SHUTDOWN_CALLBACKS << [object.method(method), args]}
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
module_function
|
79
|
+
|
80
|
+
# Plezi event cycle settings: gets how many worker threads Plezi will initially run.
|
81
|
+
def max_threads
|
82
|
+
@max_threads ||= 16
|
83
|
+
end
|
84
|
+
# Plezi event cycle settings: sets how many worker threads Plezi will initially run.
|
85
|
+
def max_threads= value
|
86
|
+
raise "Plezi will hang and do nothing if there isn't at least one (1) working thread. Cannot set Plezi.max_threads = #{value}" if value.to_i <= 0
|
87
|
+
@max_threads = value.to_i
|
88
|
+
start @max_threads if EventMachine.running?
|
89
|
+
end
|
90
|
+
|
91
|
+
# Adds a callback to be called once the services were shut down. see: callback for more info.
|
92
|
+
def on_shutdown object=nil, method=nil, *args, &block
|
93
|
+
EventMachine.on_shutdown object, method, *args, &block
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,265 @@
|
|
1
|
+
module Plezi
|
2
|
+
module EventMachine
|
3
|
+
|
4
|
+
# a basic IO listening socket wrapper.
|
5
|
+
class BasicIO
|
6
|
+
|
7
|
+
# attribute readers
|
8
|
+
attr_reader :io, :params
|
9
|
+
# initialize the listener with the actual socket and the properties.
|
10
|
+
def initialize io, params
|
11
|
+
@io, @params = io, params
|
12
|
+
end
|
13
|
+
# returns true if the socket is closed and the object can be cleared.
|
14
|
+
def clear?
|
15
|
+
@io.closed?
|
16
|
+
end
|
17
|
+
# accepts a new connection, adding it to the IO stack, so that it will be reviewed.
|
18
|
+
def call
|
19
|
+
begin
|
20
|
+
socket = io.accept_nonblock
|
21
|
+
handler = (@params[:ssl] ? SSLConnection : Connection).new socket, @params
|
22
|
+
EventMachine.add_io socket, handler
|
23
|
+
rescue Errno::EWOULDBLOCK => e
|
24
|
+
|
25
|
+
rescue OpenSSL::SSL::SSLError => e
|
26
|
+
Plezi.info "Due to an SSL issue, the connection was refused (maybe untrusted certificate)?"
|
27
|
+
|
28
|
+
rescue => e
|
29
|
+
Plezi.error e
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
module_function
|
34
|
+
|
35
|
+
# the IO stack.
|
36
|
+
EM_IO = {}
|
37
|
+
# the IO Mutex
|
38
|
+
IO_LOCKER = Mutex.new
|
39
|
+
|
40
|
+
# Adds an io object to the Plezi event machine.
|
41
|
+
#
|
42
|
+
# accepts:
|
43
|
+
# io:: an actual IO object (i.e. socket) that can be passed on to IO.select.
|
44
|
+
# job:: an object that answers to #call. job#call will be called whenever the IO object is flagged by IO.select.
|
45
|
+
#
|
46
|
+
def add_io io, job
|
47
|
+
IO_LOCKER.synchronize { EM_IO[io] = job }
|
48
|
+
end
|
49
|
+
|
50
|
+
# removes an IO from the event machine.
|
51
|
+
def remove_io io
|
52
|
+
IO_LOCKER.synchronize { (EM_IO.delete io).close rescue true }
|
53
|
+
end
|
54
|
+
|
55
|
+
# deletes any connections that are closed or timed out.
|
56
|
+
def clear_connections
|
57
|
+
IO_LOCKER.synchronize { EM_IO.delete_if { |io, c| c.clear? } }
|
58
|
+
end
|
59
|
+
|
60
|
+
# forces the event machine to forget all the existing connections (they will not be reviewed any longer).
|
61
|
+
def forget_connections
|
62
|
+
IO_LOCKER.synchronize { EM_IO.clear }
|
63
|
+
end
|
64
|
+
|
65
|
+
# stops all the connections without stopping the lisntener IO's.
|
66
|
+
def stop_connections
|
67
|
+
IO_LOCKER.synchronize { EM_IO.each { |io, c| Plezi.run_async { c.disconnect rescue true } } }
|
68
|
+
end
|
69
|
+
|
70
|
+
# set the default idle waiting time.
|
71
|
+
@io_timeout = 0.1
|
72
|
+
|
73
|
+
# Plezi event cycle settings: how long to wait for IO activity before forcing another cycle.
|
74
|
+
#
|
75
|
+
# No timing methods will be called during this interval.
|
76
|
+
#
|
77
|
+
# Gets the current idle setting. The default setting is 0.1 seconds.
|
78
|
+
def io_timeout
|
79
|
+
@io_timeout
|
80
|
+
end
|
81
|
+
# Plezi event cycle settings: how long to wait for IO activity before forcing another cycle.
|
82
|
+
#
|
83
|
+
# No timing methods will be called during this interval (#run_after / #run_every).
|
84
|
+
#
|
85
|
+
# It's rare, but it's one of the reasons for the timeout: some connections might wait for the timeout befor being established.
|
86
|
+
#
|
87
|
+
# set the current idle setting
|
88
|
+
def io_timeout= value
|
89
|
+
@io_timeout = value
|
90
|
+
end
|
91
|
+
|
92
|
+
# the proc for async IO removal, in case of IO exceptions raised by unexpectedly closed sockets.
|
93
|
+
IO_CLEAR_ASYNC_PROC = Proc.new {|c| c.protocol.on_disconnect if (c.protocol rescue false) }
|
94
|
+
|
95
|
+
# hangs for IO data or io_timeout
|
96
|
+
def review_io
|
97
|
+
return false if IO_LOCKER.locked?
|
98
|
+
IO_LOCKER.synchronize do
|
99
|
+
return false unless queue_empty? && EM_IO.any?
|
100
|
+
io_array = EM_IO.keys
|
101
|
+
begin
|
102
|
+
io_r = ::IO.select(io_array, nil, io_array, @io_timeout)
|
103
|
+
return false unless io_r
|
104
|
+
io_r[0].each {|io| queue [], EM_IO[io] }
|
105
|
+
io_r[2].each { |io| EM_IO.delete io }
|
106
|
+
rescue Errno::EWOULDBLOCK => e
|
107
|
+
|
108
|
+
rescue => e
|
109
|
+
EM_IO.keys.each {|io| EventMachine.queue [EM_IO.delete(io)], IO_CLEAR_ASYNC_PROC if io.closed?}
|
110
|
+
raise e
|
111
|
+
end
|
112
|
+
true
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
|
118
|
+
module DSL
|
119
|
+
module_function
|
120
|
+
|
121
|
+
# public API to add a service to the framework.
|
122
|
+
# accepts a Hash object with any of the following options (Hash keys):
|
123
|
+
# port:: port number. defaults to 3000 or the port specified when the script was called.
|
124
|
+
# host:: the host name. defaults to any host not explicitly defined (a catch-all).
|
125
|
+
# alias:: a String or an Array of Strings which represent alternative host names (i.e. `alias: ["admin.google.com", "admin.gmail.com"]`).
|
126
|
+
# root:: the public root folder. if this is defined, static files will be served from the location.
|
127
|
+
# assets:: the assets root folder. defaults to nil (no assets support). if the path is defined, assets will be served from `/assets/...` (or the public_asset path defined) before any static files. assets will not be served if the file in the /public/assets folder if up to date (a rendering attempt will be made for systems that allow file writing).
|
128
|
+
# assets_public:: the assets public uri location (uri format, NOT a file path). defaults to `/assets`. assets will be saved (or rendered) to the assets public folder and served as static files.
|
129
|
+
# assets_callback:: a method that accepts one parameters: `request` and renders any custom assets. the method should return `false` unless it has created a response object (`response = Plezi::HTTPResponse.new(request)`) and sent a response to the client using `response.finish`.
|
130
|
+
# save_assets:: saves the rendered assets to the filesystem, under the public folder. defaults to false.
|
131
|
+
# templates:: the templates root folder. defaults to nil (no template support). templates can be rendered by a Controller class, using the `render` method.
|
132
|
+
# ssl:: if true, an SSL service will be attempted. if no certificate is defined, an attempt will be made to create a self signed certificate.
|
133
|
+
# ssl_key:: the public key for the SSL service.
|
134
|
+
# ssl_cert:: the certificate for the SSL service.
|
135
|
+
#
|
136
|
+
# some further options, which are unstable and might be removed in future versions, are:
|
137
|
+
# protocol:: the protocol objects (usually a class, but any object answering `#call` will do).
|
138
|
+
# handler:: an optional handling object, to be called upon by the protocol (i.e. #on_message, #on_connect, etc'). this option is used to allow easy protocol switching, such as from HTTP to Websockets.
|
139
|
+
#
|
140
|
+
# Duringn normal Plezi behavior, the optional `handler` object will be returned if `listen` is called more than once for the same port.
|
141
|
+
#
|
142
|
+
# assets:
|
143
|
+
#
|
144
|
+
# assets support will render `.sass`, `.scss` and `.coffee` and save them as local files (`.css`, `.css`, and `.js` respectively)
|
145
|
+
# before sending them as static files.
|
146
|
+
#
|
147
|
+
# templates:
|
148
|
+
#
|
149
|
+
# ERB, Slim and Haml are natively supported.
|
150
|
+
#
|
151
|
+
def listen parameters = {}
|
152
|
+
# update default values
|
153
|
+
parameters = {assets_public: '/assets'}.update(parameters)
|
154
|
+
|
155
|
+
# set port if undefined
|
156
|
+
if !parameters[:port] && defined? ARGV
|
157
|
+
if ARGV.find_index('-p')
|
158
|
+
port_index = ARGV.find_index('-p') + 1
|
159
|
+
parameters[:port] ||= ARGV[port_index].to_i
|
160
|
+
ARGV[port_index] = (parameters[:port] + 1).to_s
|
161
|
+
else
|
162
|
+
ARGV << '-p'
|
163
|
+
ARGV << '3001'
|
164
|
+
parameters[:port] ||= 3000
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
#keeps information of past ports.
|
169
|
+
@listeners ||= {}
|
170
|
+
@listeners_locker = Mutex.new
|
171
|
+
|
172
|
+
# check if the port is used twice.
|
173
|
+
if @listeners[parameters[:port]]
|
174
|
+
puts "WARNING: port aleady in use! returning existing service and attemptin to add host (maybe multiple hosts? use `host` instead)." unless parameters[:host]
|
175
|
+
@active_router = @listeners[parameters[:port]].params[:handler] || @listeners[parameters[:port]].params[:protocol]
|
176
|
+
@active_router.add_host parameters[:host], parameters if @active_router.is_a?(HTTPRouter)
|
177
|
+
return @active_router
|
178
|
+
end
|
179
|
+
|
180
|
+
# make sure the protocol exists.
|
181
|
+
unless parameters[:protocol]
|
182
|
+
parameters[:protocol] = HTTPProtocol
|
183
|
+
parameters[:handler] ||= HTTPRouter.new
|
184
|
+
end
|
185
|
+
|
186
|
+
# create the EventMachine IO object.
|
187
|
+
io = defined?(PLEZI_ON_RACK) ? parameters : EventMachine::BasicIO.new( TCPServer.new(parameters[:port]), parameters)
|
188
|
+
EventMachine.add_io io.io, io unless defined? PLEZI_ON_RACK
|
189
|
+
@listeners_locker.synchronize { @listeners[parameters[:port]] = io }
|
190
|
+
# set the active router to the handler or the protocol.
|
191
|
+
@active_router = (parameters[:handler] || parameters[:protocol])
|
192
|
+
@active_router.add_host(parameters[:host], parameters) if @active_router.is_a?(HTTPRouter)
|
193
|
+
|
194
|
+
Plezi.run_async { Plezi.info "Started listening on port #{parameters[:port]}." } unless defined?(PLEZI_ON_RACK)
|
195
|
+
|
196
|
+
# return the current handler or the protocol..
|
197
|
+
@active_router
|
198
|
+
end
|
199
|
+
|
200
|
+
# Plezi Engine, DO NOT CALL. creates the thread pool and starts cycling through the events.
|
201
|
+
def start_services
|
202
|
+
if @listeners && @listeners.any?
|
203
|
+
# prepare threads
|
204
|
+
exit_flag = false
|
205
|
+
threads = []
|
206
|
+
Plezi.run_every(5, &EventMachine.method(:clear_connections))
|
207
|
+
Plezi.run_every(3_600) {GC.start; Plezi.info "Refreshing worker threads."; EventMachine.stop; EventMachine.start Plezi.max_threads}
|
208
|
+
# run_every( 1 , Proc.new() { Plezi.info "#{IO_CONNECTION_DIC.length} active connections ( #{ IO_CONNECTION_DIC.select{|k,v| v.protocol.is_a?(WSProtocol)} .length } websockets)." })
|
209
|
+
# run_every 10 , -> {Plezi.info "Cache report: #{CACHE_STORE.length} objects cached." }
|
210
|
+
puts "Services running Plezi version #{Plezi::VERSION}. Press ^C to stop"
|
211
|
+
EventMachine.start Plezi.max_threads
|
212
|
+
|
213
|
+
# set signal tarps
|
214
|
+
trap('INT'){ exit_flag = true; raise "close Plezi" }
|
215
|
+
trap('TERM'){ exit_flag = true; raise "close Plezi" }
|
216
|
+
# sleep until trap raises exception (cycling might cause the main thread to ignor signals and lose attention)
|
217
|
+
sleep rescue true
|
218
|
+
# avoid refreshing the working threads and stop all timed events.
|
219
|
+
EventMachine.clear_timers
|
220
|
+
# start shutdown.
|
221
|
+
exit_flag = true
|
222
|
+
# set new tarps
|
223
|
+
trap('INT'){ puts 'Forced exit.'; Kernel.exit } #rescue true}
|
224
|
+
trap('TERM'){ puts 'Forced exit.'; Kernel.exit } #rescue true }
|
225
|
+
puts 'Started shutdown process. Press ^C to force quit.'
|
226
|
+
# shut down listening sockets
|
227
|
+
stop_services
|
228
|
+
# cycle down threads
|
229
|
+
Plezi.info "Finishing up and running shutdown tasks."
|
230
|
+
end
|
231
|
+
EventMachine.shutdown
|
232
|
+
Plezi.info "Plezi shutdown as requested."
|
233
|
+
puts "Since we're resting, why not practice Tai Chi?"
|
234
|
+
# return exit code?
|
235
|
+
0
|
236
|
+
end
|
237
|
+
# Closes and removes listening IO's registered by Plezi using #listen
|
238
|
+
def stop_services
|
239
|
+
Plezi.info 'Stopping services'
|
240
|
+
@listeners_locker.synchronize { @listeners.each {|port, io| EventMachine.remove_io(io.io); Plezi.info "Stoped listening on port #{port}" } ; @listeners.clear } if @listeners
|
241
|
+
true
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
module_function
|
246
|
+
|
247
|
+
# Plezi event cycle settings: how long to wait for IO activity before forcing another cycle.
|
248
|
+
#
|
249
|
+
# No timing methods will be called during this interval.
|
250
|
+
#
|
251
|
+
# Gets the current idle setting. The default setting is 0.1 seconds.
|
252
|
+
def idle_sleep
|
253
|
+
EventMachine.io_timeout
|
254
|
+
end
|
255
|
+
# Plezi event cycle settings: how long to wait for IO activity before forcing another cycle.
|
256
|
+
#
|
257
|
+
# No timing methods will be called during this interval (#run_after / #run_every).
|
258
|
+
#
|
259
|
+
# It's rare, but it's one of the reasons for the timeout: some connections might wait for the timeout befor being established.
|
260
|
+
#
|
261
|
+
# set the current idle setting
|
262
|
+
def idle_sleep= value
|
263
|
+
EventMachine.io_timeout = value
|
264
|
+
end
|
265
|
+
end
|