plezi 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +22 -0
  3. data/CHANGELOG.md +450 -0
  4. data/Gemfile +4 -0
  5. data/KNOWN_ISSUES.md +13 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +341 -0
  8. data/Rakefile +2 -0
  9. data/TODO.md +19 -0
  10. data/bin/plezi +301 -0
  11. data/lib/plezi.rb +125 -0
  12. data/lib/plezi/base/cache.rb +77 -0
  13. data/lib/plezi/base/connections.rb +33 -0
  14. data/lib/plezi/base/dsl.rb +177 -0
  15. data/lib/plezi/base/engine.rb +85 -0
  16. data/lib/plezi/base/events.rb +84 -0
  17. data/lib/plezi/base/io_reactor.rb +41 -0
  18. data/lib/plezi/base/logging.rb +62 -0
  19. data/lib/plezi/base/rack_app.rb +89 -0
  20. data/lib/plezi/base/services.rb +57 -0
  21. data/lib/plezi/base/timers.rb +71 -0
  22. data/lib/plezi/handlers/controller_magic.rb +383 -0
  23. data/lib/plezi/handlers/http_echo.rb +27 -0
  24. data/lib/plezi/handlers/http_host.rb +215 -0
  25. data/lib/plezi/handlers/http_router.rb +69 -0
  26. data/lib/plezi/handlers/magic_helpers.rb +43 -0
  27. data/lib/plezi/handlers/route.rb +272 -0
  28. data/lib/plezi/handlers/stubs.rb +143 -0
  29. data/lib/plezi/server/README.md +33 -0
  30. data/lib/plezi/server/helpers/http.rb +169 -0
  31. data/lib/plezi/server/helpers/mime_types.rb +999 -0
  32. data/lib/plezi/server/protocols/http_protocol.rb +318 -0
  33. data/lib/plezi/server/protocols/http_request.rb +133 -0
  34. data/lib/plezi/server/protocols/http_response.rb +294 -0
  35. data/lib/plezi/server/protocols/websocket.rb +208 -0
  36. data/lib/plezi/server/protocols/ws_response.rb +92 -0
  37. data/lib/plezi/server/services/basic_service.rb +224 -0
  38. data/lib/plezi/server/services/no_service.rb +196 -0
  39. data/lib/plezi/server/services/ssl_service.rb +193 -0
  40. data/lib/plezi/version.rb +3 -0
  41. data/plezi.gemspec +26 -0
  42. data/resources/404.erb +68 -0
  43. data/resources/404.haml +64 -0
  44. data/resources/404.html +67 -0
  45. data/resources/404.slim +63 -0
  46. data/resources/500.erb +68 -0
  47. data/resources/500.haml +63 -0
  48. data/resources/500.html +67 -0
  49. data/resources/500.slim +63 -0
  50. data/resources/Gemfile +85 -0
  51. data/resources/anorexic_gray.png +0 -0
  52. data/resources/anorexic_websockets.html +47 -0
  53. data/resources/code.rb +8 -0
  54. data/resources/config.ru +39 -0
  55. data/resources/controller.rb +139 -0
  56. data/resources/db_ac_config.rb +58 -0
  57. data/resources/db_dm_config.rb +51 -0
  58. data/resources/db_sequel_config.rb +42 -0
  59. data/resources/en.yml +204 -0
  60. data/resources/environment.rb +41 -0
  61. data/resources/haml_config.rb +6 -0
  62. data/resources/i18n_config.rb +14 -0
  63. data/resources/rakefile.rb +22 -0
  64. data/resources/redis_config.rb +35 -0
  65. data/resources/routes.rb +26 -0
  66. data/resources/welcome_page.html +72 -0
  67. data/websocket chatroom.md +639 -0
  68. metadata +141 -0
@@ -0,0 +1,125 @@
1
+ ### Ruby core extentions
2
+
3
+ require 'singleton'
4
+ require 'pathname'
5
+ require 'logger'
6
+ require 'socket'
7
+ require 'openssl'
8
+ require 'strscan'
9
+ require 'base64'
10
+ require 'digest/sha1'
11
+ require 'securerandom'
12
+ require 'time'
13
+ require 'json'
14
+
15
+ ### version
16
+
17
+ require "plezi/version"
18
+
19
+
20
+ ### Server requirements
21
+
22
+ require "plezi/server/services/basic_service"
23
+ require "plezi/server/services/ssl_service"
24
+ require "plezi/server/services/no_service"
25
+
26
+ require "plezi/server/protocols/http_protocol"
27
+ require 'plezi/server/protocols/http_request'
28
+ require 'plezi/server/protocols/http_response'
29
+
30
+ require "plezi/server/helpers/http"
31
+ require "plezi/server/helpers/mime_types"
32
+
33
+ require "plezi/server/protocols/websocket"
34
+ require 'plezi/server/protocols/ws_response'
35
+
36
+ ## Server-Framework Bridges
37
+ require "plezi/handlers/http_echo"
38
+ require "plezi/handlers/http_host"
39
+ require "plezi/handlers/http_router"
40
+
41
+ require "plezi/handlers/controller_magic"
42
+ require "plezi/handlers/magic_helpers"
43
+ require "plezi/handlers/route"
44
+
45
+ require "plezi/handlers/stubs"
46
+
47
+ ### Framework requirements
48
+ require "plezi/base/events"
49
+ require "plezi/base/timers"
50
+ require "plezi/base/services"
51
+ require "plezi/base/connections"
52
+ require "plezi/base/logging"
53
+ require "plezi/base/io_reactor"
54
+ require "plezi/base/cache"
55
+ require "plezi/base/engine"
56
+
57
+ ### DSL requirements
58
+ require "plezi/base/dsl"
59
+
60
+ ### optional Rack
61
+ require "plezi/base/rack_app"
62
+
63
+ ## erb templating
64
+ begin
65
+ require 'erb'
66
+ rescue Exception => e
67
+
68
+ end
69
+
70
+
71
+
72
+ ##############################################################################
73
+ # To make something new, we leap to the unknown.
74
+ ##############################################################################
75
+ # Plezi is a stand alone web services app, which supports RESTful HTTP, HTTP Streaming and WebSockets.
76
+ #
77
+ # Plezi is a wonderful alternative to Socket.io which makes writing the server using Ruby a breeze.
78
+ #
79
+ # Plezi routes accept Regexp's (regular exceptions) for route paths. for example:
80
+ #
81
+ # require 'plezi'
82
+ # listen
83
+ # route(/[.]*/) {|request, response| response << "Your request, master: #{request.path}."}
84
+ #
85
+ # The catch-all route (/[.]*/) has a shortcut '*', so it's possible to write:
86
+ #
87
+ # require 'plezi'
88
+ # listen
89
+ # route('*') {|request, response| response << "Your request, master: #{request.path}."}
90
+ #
91
+ #
92
+ # Plezi accepts an optional class object that can be passed using the `route` command. Passing a class object is especially useful for RESTful and WebSocket applications.
93
+ # read more at the Plezi::StubWSCtrl and Plezi::StubRESTCtrl documentation, which are stub classes used for testing routes.
94
+ #
95
+ # require 'plezi'
96
+ # listen
97
+ # route "*", Plezi::StubRESTCtrl
98
+ #
99
+ # class routes that have a specific path (including root, but not a catch-all or Regexp path)
100
+ # accept an implied `params[:id]` variable. the following path ('/'):
101
+ #
102
+ # require 'plezi'
103
+ # listen
104
+ # route "/", Plezi::StubRESTCtrl
105
+ # # client requests: /1
106
+ # # => Plezi::StubRESTCtrl.new.show() # where params[:id] == 1
107
+ #
108
+ # it is possible to use "magic" routes (i.e. `/resource/:type/(:id)/(:date){/[0-9]{8}}/:foo`) and it is also possible to set the appropriate paramaters within the `before` method of the Conltroller.
109
+ #
110
+ # Routes are handled in the order they are created. If overlapping routes exist, the first will execute first:
111
+ #
112
+ # require 'plezi'
113
+ # listen
114
+ # route('*') do |request, response|
115
+ # response << "Your request, master: #{request.path}." unless request.path.match /cats/
116
+ # end
117
+ # route('*') {|request, response| response.body << "Ahhh... I love cats!"}
118
+ #
119
+ # all the examples above shuold be good to run from irb. updated examples can be found at the Readme file in the Github project: https://github.com/boazsegev/plezi
120
+ #
121
+ # thanks to Russ Olsen for his ideas for a DSL and his blog post at:
122
+ # http://www.jroller.com/rolsen/entry/building_a_dsl_in_ruby1
123
+ ##############################################################################
124
+ module Plezi
125
+ end
@@ -0,0 +1,77 @@
1
+
2
+ module Plezi
3
+
4
+ # File and Object Caching for Plezi
5
+ module_function
6
+ # contains the cached data, in the format: CACHE_STORE["filename"] = CacheObject
7
+ CACHE_STORE = {}
8
+ LOCK = Mutex.new
9
+ CACHABLE = %w{cache object slim haml css map js html scss sass coffee txt xml json yaml rb}
10
+
11
+ @cache_to_disk = true
12
+
13
+ # this class holds cached objects (data and modification times)
14
+ class CacheObject
15
+ # Cached attributes
16
+ attr_accessor :data, :mtime
17
+
18
+ # initialize a Cached object
19
+ def initialize d = nil, t = Time.now
20
+ @data, @mtime = d, t
21
+ end
22
+ end
23
+
24
+ # load the file from the cache (if exists) or the file system (if it doesn't)
25
+ def load_file filename
26
+ cached?(filename) ? get_cached(filename) : reload_file(filename)
27
+ end
28
+ # review a file's modification time
29
+ def file_mtime filename
30
+ return CACHE_STORE[filename].mtime if cached?(filename)
31
+ File.mtime(filename)
32
+ end
33
+
34
+ # force a file onto the cache (only if it is cachable - otherwise will load the file but will not cache it).
35
+ def reload_file filename
36
+ if CACHABLE.include? filename.match(/\.([^\.]+)$/)[1]
37
+ return cache_data filename, IO.read(filename), File.mtime(filename)
38
+ else
39
+ return IO.read(filename)
40
+ end
41
+ end
42
+ # places data into the cache, and attempts to save the data to a file name.
43
+ def save_file filename, data, save_to_disk = false
44
+ cache_data filename, data if CACHABLE.include? filename.match(/\.([^\.]+)$/)[1]
45
+ begin
46
+ IO.write filename, data if save_to_disk
47
+ rescue Exception => e
48
+ Plezi.warn("File couldn't be written (#{filename}) - file system error?")
49
+ end
50
+ data
51
+ end
52
+ # places data into the cache, under an identifier ( file name ).
53
+ def cache_data filename, data, mtime = Time.now
54
+ LOCK.synchronize { CACHE_STORE[filename] = CacheObject.new( data, mtime ) }
55
+ data
56
+ end
57
+ # Get data from the cache. will throw an exception if there is no data in the cache.
58
+ def get_cached filename
59
+ CACHE_STORE[filename].data # if CACHE_STORE[filename]
60
+ end
61
+
62
+ # returns true if the filename is cached.
63
+ def cached? filename
64
+ !CACHE_STORE[filename].nil?
65
+ end
66
+
67
+ # returns true if the file exists on disk or in the cache.
68
+ def file_exists? filename
69
+ (CACHE_STORE[filename] || File.exists?(filename)) ? true : false
70
+ end
71
+
72
+ # returns true if the file has been update since data was last cached.
73
+ def cache_needs_update? filename
74
+ return true if CACHE_STORE[filename].nil? || CACHE_STORE[filename].mtime < File.mtime(filename)
75
+ false
76
+ end
77
+ end
@@ -0,0 +1,33 @@
1
+
2
+ module Plezi
3
+
4
+ module_function
5
+
6
+ # DANGER ZONE - Plezi Engine. the connections store
7
+ IO_CONNECTION_DIC = {}
8
+ # DANGER ZONE - Plezi Engine. the connections mutex
9
+ C_LOCKER = Mutex.new
10
+
11
+ # Plezi Engine, DO NOT CALL. disconnectes all active connections
12
+ def stop_connections
13
+ log 'Stopping connections'
14
+ C_LOCKER.synchronize {IO_CONNECTION_DIC.values.each {|c| c.timeout = -1; callback c, :on_disconnect unless c.disconnected?} ; IO_CONNECTION_DIC.clear}
15
+ end
16
+
17
+ # Plezi Engine, DO NOT CALL. adds a new connection to the connection stack
18
+ def add_connection io, params
19
+ connection = params[:service_type].new(io, params)
20
+ C_LOCKER.synchronize {IO_CONNECTION_DIC[connection.socket] = connection} if connection
21
+ callback(connection, :on_message)
22
+ end
23
+ # Plezi Engine, DO NOT CALL. removes a connection from the connection stack
24
+ def remove_connection connection
25
+ C_LOCKER.synchronize { IO_CONNECTION_DIC.delete connection.socket }
26
+ end
27
+
28
+ # clears closed connections from the stack
29
+ def clear_connections
30
+ C_LOCKER.synchronize { IO_CONNECTION_DIC.values.each {|c| callback c, :on_disconnect if c.disconnected? || c.timedout? } }
31
+ end
32
+
33
+ end
@@ -0,0 +1,177 @@
1
+
2
+ module Plezi
3
+
4
+ # this module contains the methods that are used as a DSL and sets up easy access to the Plezi framework.
5
+ #
6
+ # use the`listen`, `host` and `route` functions rather then accessing this object.
7
+ #
8
+ module DSL
9
+ module_function
10
+
11
+ @servers = {}
12
+ @active_router = nil
13
+
14
+ # adds a server (performs the action required by the listen method).
15
+ #
16
+ # accepts:
17
+ # port:: (optional) the port number for the service. if not defined, defaultes to the -p runtime argument (i.e. `./app.rb -p 8080`) or to 3000 if argument is missing.
18
+ # params:: any parameter accepted by the Plezi.add_service method. defaults to: `:protocol=>Plezi::HTTPProtocol, :handler => HTTPRouter.new`
19
+ def listen(port, params = {})
20
+ # set port and arguments
21
+ if port.is_a?(Hash)
22
+ params = port
23
+ port = nil
24
+ end
25
+ if !port && defined? ARGV
26
+ if ARGV.find_index('-p')
27
+ port_index = ARGV.find_index('-p') + 1
28
+ port ||= ARGV[port_index].to_i
29
+ ARGV[port_index] = (port + 1).to_s
30
+ else
31
+ ARGV << '-p'
32
+ ARGV << '3000'
33
+ return listen nil, params
34
+ end
35
+ end
36
+ port ||= 3000
37
+
38
+ # create new service or choose existing
39
+ if @servers[port]
40
+ puts "WARNING: port aleady in use! returning existing service and attemptin to add host (maybe multiple hosts? use `host` instead)." unless params[:host]
41
+ @active_router = @servers[port][:handler]
42
+ @active_router.add_host params[:host], params
43
+ return @active_router
44
+ end
45
+ params[:protocol] ||= HTTPProtocol
46
+ @active_router = params[:handler] ||= HTTPRouter.new # HTTPEcho #
47
+ @active_router.add_host params[:host], params
48
+ return false unless Plezi.add_service(port, params)
49
+ @servers[port] = params
50
+ @active_router
51
+ end
52
+
53
+ # adds a route to the last server created
54
+ def route(path, controller = nil, &block)
55
+ @active_router.add_route path, controller, &block
56
+ end
57
+
58
+
59
+ # adds a shared route to all existing services and hosts.
60
+ def shared_route(path, controller = nil, &block)
61
+ @servers.values.each {|p| p[:handler].add_shared_route path, controller, &block }
62
+ end
63
+
64
+ # adds a host to the last server created
65
+ #
66
+ # accepts the same parameter(s) as the `listen` command (see Plezi.add_service), except :protocol and :handler are ignored:
67
+ # alias:: a String or an Array of Strings which represent alternative host names (i.e. `alias: ["admin.google.com", "admin.gmail.com"]`).
68
+ def host(host_name, params)
69
+ @active_router.add_host host_name, params
70
+ end
71
+ end
72
+ end
73
+
74
+ Encoding.default_internal = 'utf-8'
75
+ Encoding.default_external = 'utf-8'
76
+
77
+ # Set a shortcut for the Plezi module.
78
+ PL = Plezi
79
+
80
+ # creates a server object and waits for routes to be set.
81
+ #
82
+ # port:: the port to listen to. the first port defaults to 3000 and increments by 1 with every `listen` call. it's possible to set the first port number by running the app with the -p paramater.
83
+ # params:: a Hash of serever paramaters, as listed in the Plezi#add_service documentation.
84
+ #
85
+ # The different keys in the params hash control the server's behaviour, as follows:
86
+ #
87
+ # host:: the host name. defaults to any host not explicitly defined (a catch-all).
88
+ # alias:: a String or an Array of Strings which represent alternative host names (i.e. `alias: ["admin.google.com", "admin.gmail.com"]`).
89
+ # root:: the public root folder. if this is defined, static files will be served from the location.
90
+ # 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).
91
+ # 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.
92
+ # 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`.
93
+ # save_assets:: saves the rendered assets to the filesystem, under the public folder. defaults to false.
94
+ # templates:: the templates root folder. defaults to nil (no template support). templates can be rendered by a Controller class, using the `render` method.
95
+ # 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.
96
+ # ssl_key:: the public key for the SSL service.
97
+ # ssl_cert:: the certificate for the SSL service.
98
+ #
99
+ def listen(port = nil, params = {})
100
+ Plezi::DSL.listen port, params
101
+ end
102
+
103
+ # adds a virtul host to the current service (the last `listen` call) or switches to an existing host within the active service.
104
+ #
105
+ # accepts:
106
+ # host_name: a String with the full host name (i.e. "www.google.com" / "mail.google.com")
107
+ # params:: any of the parameters accepted by the `listen` command, except `protocol`, `handler`, and `ssl` parameters.
108
+ def host(host_name = false, params = {})
109
+ Plezi::DSL.host host_name, params
110
+ end
111
+
112
+ # adds a route to the last server object
113
+ #
114
+ # path:: the path for the route
115
+ # controller:: The controller class which will accept the route.
116
+ #
117
+ # `path` paramaters has a few options:
118
+ #
119
+ # * `path` can be a Regexp object, forcing the all the logic into controller (typically using the before method).
120
+ #
121
+ # * simple String paths are assumed to be basic RESTful paths:
122
+ #
123
+ # route "/users", Controller => route "/users/(:id)", Controller
124
+ #
125
+ # * routes can define their own parameters, for their own logic:
126
+ #
127
+ # route "/path/:required_paramater/:required_paramater{with_format}/(:optional_paramater)/(:optional){with_format}"
128
+ #
129
+ # * routes can define optional or required routes with regular expressions in them:
130
+ #
131
+ # route "(:locale){en|ru}/path"
132
+ #
133
+ # * routes which use the special '/' charecter within a parameter's format, must escape this charecter using the '\' charecter. **Notice the single quotes** in the following example:
134
+ #
135
+ # route '(:math){[\d\+\-\*\^\%\.\/]}'
136
+ #
137
+ # * or, with double quotes:
138
+ #
139
+ # route "(:math){[\\d\\+\\-\\*\\^\\%\\.\\/]}"
140
+ #
141
+ # magic routes make for difficult debugging - the smarter the routes, the more difficult the debugging.
142
+ # use with care and avoid complex routes when possible. RESTful routes are recommended when possible.
143
+ # json serving apps are advised to use required paramaters, empty sections indicating missing required paramaters (i.e. /path///foo/bar///).
144
+ #
145
+ def route(path, controller = nil, &block)
146
+ Plezi::DSL.route(path, controller, &block)
147
+ end
148
+
149
+ # adds a route to the all the existing servers and hosts.
150
+ #
151
+ # accepts same options as route.
152
+ def shared_route(path, controller = nil, &block)
153
+ Plezi::DSL.shared_route(path, controller, &block)
154
+ end
155
+
156
+ # finishes setup of the servers and starts them up. This will hange the proceess.
157
+ #
158
+ # this method is called automatically by the Plezi framework.
159
+ #
160
+ # it is recommended that you DO NOT CALL this method.
161
+ # if any post shut-down actions need to be performed, use Plezi.on_shutdown instead.
162
+ def start_services
163
+ return 0 if ( defined?(NO_PLEZI_AUTO_START) || defined?(BUILDING_PLEZI_TEMPLATE) || defined?(PLEZI_ON_RACK) )
164
+ Object.const_set "NO_PLEZI_AUTO_START", true
165
+ undef listen
166
+ undef host
167
+ undef route
168
+ undef shared_route
169
+ undef start_services
170
+ Plezi.start_services
171
+ end
172
+
173
+ # sets to start the services once dsl script is finished loading.
174
+ at_exit { start_services } unless ( defined?(NO_PLEZI_AUTO_START) || defined?(BUILDING_PLEZI_TEMPLATE) || defined?(PLEZI_ON_RACK) )
175
+
176
+ # sets a name for the process (on some systems).
177
+ $0="Plezi (Ruby)"
@@ -0,0 +1,85 @@
1
+
2
+ module Plezi
3
+
4
+ module_function
5
+
6
+ # Plezi event cycle settings: gets how many worker threads Plezi will run.
7
+ def max_threads
8
+ @max_threads ||= 16
9
+ end
10
+ # Plezi event cycle settings: sets how many worker threads Plezi will run.
11
+ def max_threads= value
12
+ @max_threads = value
13
+ end
14
+
15
+ # Plezi event cycle settings: how long to wait for IO activity before forcing another cycle.
16
+ #
17
+ # No timing methods will be called during this interval.
18
+ #
19
+ # get the current idle setting
20
+ def idle_sleep
21
+ @idle_sleep ||= 0.1
22
+ end
23
+ # Plezi event cycle settings: how long to wait for IO activity before forcing another cycle.
24
+ #
25
+ # No timing methods will be called during this interval.
26
+ #
27
+ # set the current idle setting
28
+ def idle_sleep= value
29
+ @idle_sleep = value
30
+ end
31
+
32
+ # Plezi Engine, DO NOT CALL. creates the thread pool and starts cycling through the events.
33
+ def start_services
34
+ # prepare threads
35
+ exit_flag = false
36
+ threads = []
37
+ run_every(5 , Plezi.method(:clear_connections)) #{info "Cleared inactive Connections"}
38
+ run_every 3600 , GC.method(:start)
39
+ # 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)." })
40
+ (max_threads).times { Thread.new { thread_cycle until exit_flag } }
41
+
42
+ # Thread.new { check_connections until SERVICES.empty? }
43
+ #...
44
+ # set signal tarps
45
+ trap('INT'){ exit_flag = true; raise "close Plezi" }
46
+ trap('TERM'){ exit_flag = true; raise "close Plezi" }
47
+ puts 'Services running. Press ^C to stop'
48
+ # sleep until trap raises exception (cycling might cause the main thread to ignor signals and lose attention)
49
+ (sleep unless SERVICES.empty?) rescue true
50
+ # start shutdown.
51
+ exit_flag = true
52
+ # set new tarps
53
+ trap('INT'){ puts 'Forced exit.'; Kernel.exit }#rescue true}
54
+ trap('TERM'){ puts 'Forced exit.'; Kernel.exit }#rescue true }
55
+ puts 'Started shutdown process. Press ^C to force quit.'
56
+ # shut down listening sockets
57
+ stop_services
58
+ # disconnect active connections
59
+ stop_connections
60
+ # cycle down threads
61
+ info "Waiting for workers to cycle down"
62
+ threads.each {|t| t.join if t.alive?}
63
+
64
+ # rundown any active events
65
+ thread_cycle
66
+
67
+ # call shutdown callbacks
68
+ SHUTDOWN_CALLBACKS.each {|s| s[0].call(*s[1]) }
69
+ SHUTDOWN_CALLBACKS.clear
70
+
71
+ # return exit code?
72
+ 0
73
+ end
74
+
75
+ # Plezi Engine, DO NOT CALL. runs one thread cycle
76
+ def self.thread_cycle flag = 0
77
+ io_reactor rescue false # stop_connections
78
+ true while fire_event
79
+ fire_timers
80
+
81
+ rescue Exception => e
82
+
83
+ error e
84
+ end
85
+ end