iodine 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of iodine might be problematic. Click here for more details.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +75 -0
- data/Rakefile +10 -0
- data/bin/console +16 -0
- data/bin/echo +36 -0
- data/bin/em playground +56 -0
- data/bin/http_test +49 -0
- data/bin/setup +7 -0
- data/iodine.gemspec +33 -0
- data/lib/iodine.rb +17 -0
- data/lib/iodine/core.rb +112 -0
- data/lib/iodine/io.rb +110 -0
- data/lib/iodine/logging.rb +45 -0
- data/lib/iodine/protocol.rb +155 -0
- data/lib/iodine/settings.rb +98 -0
- data/lib/iodine/ssl_protocol.rb +108 -0
- data/lib/iodine/timers.rb +116 -0
- data/lib/iodine/version.rb +3 -0
- metadata +108 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 51d03f6b35a78289c742fbed134a68963bc5ddd6
|
4
|
+
data.tar.gz: 8d541074dd1f8fbde916034e659b28816417e863
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3365f3efe45797ab9fef24fd61e003425debbc680dc190ca14ffc7797fda812e520c1849a95232f199e3a50b220d04f37e8e922d8a72a8cc65bc1eaa605cfec7
|
7
|
+
data.tar.gz: 3cb5df7d237484fe18fd8828f33a682d210339821322b7c2e709a826bde3cb66c9e0d638a5843b46e28fbab56cf09a0203bed7848ec3383174d3399ab2190c8f
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 Boaz Segev
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
# Iodine
|
2
|
+
|
3
|
+
Iodine makes writing evented server applications easy to write.
|
4
|
+
|
5
|
+
Iodine is intended to replace the use of a generic reacor, such as EventMachine or GReactor and it hides all the nasty details of creating the event loop.
|
6
|
+
|
7
|
+
To use Iodine, you just set up your tasks - including a single server, if you want one. Iodine will start running once your application is finished and it won't stop runing until all the tasks have completed.
|
8
|
+
|
9
|
+
Iodine v. 0.0.1 isn't well tested just yet... but I'm releasing it anyway, to reserve the name and because initial testing shows that it works.
|
10
|
+
|
11
|
+
## Installation
|
12
|
+
|
13
|
+
Add this line to your application's Gemfile:
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
gem 'iodine'
|
17
|
+
```
|
18
|
+
|
19
|
+
And then execute:
|
20
|
+
|
21
|
+
$ bundle
|
22
|
+
|
23
|
+
Or install it yourself as:
|
24
|
+
|
25
|
+
$ gem install iodine
|
26
|
+
|
27
|
+
## Simple Usage
|
28
|
+
|
29
|
+
Iodine starts to work once you app is finished with setting all the tasks up (upon exit).
|
30
|
+
|
31
|
+
To see how that works, open your `irb` terminal an try this:
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
require 'iodine'
|
35
|
+
|
36
|
+
# Iodine supports shutdown hooks
|
37
|
+
Iodine.on_shutdown { puts "Done!" }
|
38
|
+
# The last hook is the first scheduled for execution
|
39
|
+
Iodine.on_shutdown { puts "Finishing up :-)" }
|
40
|
+
|
41
|
+
# Setup tasks using the `run` or `callback` methods
|
42
|
+
Iodine.run do
|
43
|
+
# tasks can create more tasks...
|
44
|
+
Iodine.run { puts "Task 2 completed!" }
|
45
|
+
puts "Task 1 completed!"
|
46
|
+
end
|
47
|
+
|
48
|
+
# set concurrency level (defaults to a single thread).
|
49
|
+
Iodine.threads = 5
|
50
|
+
|
51
|
+
# Iodine will start executing tasks once your script is done.
|
52
|
+
exit
|
53
|
+
```
|
54
|
+
|
55
|
+
## Server Usage
|
56
|
+
|
57
|
+
Iodine is designed to help write network services (Servers) where each script is intended to implement a single server.
|
58
|
+
|
59
|
+
This is not a philosophy based on any idea or preferences, but rather a response to real-world design where each Ruby script is usually assigned a single port for network access (hence, a single server).
|
60
|
+
|
61
|
+
## Development
|
62
|
+
|
63
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
64
|
+
|
65
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
66
|
+
|
67
|
+
## Contributing
|
68
|
+
|
69
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/iodine.
|
70
|
+
|
71
|
+
|
72
|
+
## License
|
73
|
+
|
74
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
75
|
+
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'benchmark'
|
4
|
+
$LOAD_PATH.unshift File.expand_path(File.join('..', '..', 'lib'), __FILE__ )
|
5
|
+
require "bundler/setup"
|
6
|
+
require "iodine"
|
7
|
+
|
8
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
9
|
+
# with your gem easier. You can also use a different console, if you like.
|
10
|
+
|
11
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
12
|
+
# require "pry"
|
13
|
+
# Pry.start
|
14
|
+
|
15
|
+
require "irb"
|
16
|
+
IRB.start
|
data/bin/echo
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'pathname'
|
4
|
+
Root ||= Pathname.new(File.dirname(__FILE__)).expand_path
|
5
|
+
Dir.chdir Root.join('..').to_s
|
6
|
+
|
7
|
+
require "bundler/setup"
|
8
|
+
require "iodine"
|
9
|
+
require 'stringio'
|
10
|
+
|
11
|
+
# ab -n 10000 -c 200 -k http://127.0.0.1:3000/ctrl
|
12
|
+
# ~/ruby/wrk/wrk -c400 -d10 -t12 http://localhost:3000/ctrl
|
13
|
+
|
14
|
+
|
15
|
+
|
16
|
+
class EchoServer < Iodine::Protocol
|
17
|
+
def on_message data
|
18
|
+
write("-- Closing connection, goodbye.\n") && close if data =~ /^(bye|close|exit|stop)/i
|
19
|
+
write(">> #{data.chomp}\n")
|
20
|
+
end
|
21
|
+
|
22
|
+
def ping
|
23
|
+
write "-- Are you still there?\n"
|
24
|
+
end
|
25
|
+
|
26
|
+
def on_close
|
27
|
+
Iodine.info "Closed connection."
|
28
|
+
end
|
29
|
+
def on_open
|
30
|
+
Iodine.info "Opened connection."
|
31
|
+
set_timeout 5
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
Iodine.protocol = EchoServer
|
data/bin/em playground
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#encoding: UTF-8
|
3
|
+
|
4
|
+
require 'eventmachine'
|
5
|
+
require 'stringio'
|
6
|
+
|
7
|
+
|
8
|
+
module MiniServer
|
9
|
+
def post_init
|
10
|
+
comm_inactivity_timeout = 5
|
11
|
+
@headers = {}
|
12
|
+
# start_tls :private_key_file => 'server.key', :cert_chain_file => 'server.crt', :verify_peer => false
|
13
|
+
end
|
14
|
+
|
15
|
+
def receive_data data
|
16
|
+
# EventMachine.defer do
|
17
|
+
data = ::StringIO.new data
|
18
|
+
l = nil
|
19
|
+
headers = @headers
|
20
|
+
while (l = data.gets)
|
21
|
+
unless l =~ /^[\r]?\n/
|
22
|
+
if l.include? ':'
|
23
|
+
l = l.strip.downcase.split(':', 2)
|
24
|
+
headers[l[0]] = l[1]
|
25
|
+
else
|
26
|
+
headers[:method], headers[:query], headers[:version] = l.split(/[\s]+/, 3)
|
27
|
+
end
|
28
|
+
next
|
29
|
+
end
|
30
|
+
if headers['connection'] =~ /keep/i || headers[:version] =~ /1\.1/
|
31
|
+
send_data "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 12\r\nConnection: keep-alive\r\nKeep-Alive: 5\r\n\r\nhello world\n"
|
32
|
+
else
|
33
|
+
send_data "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 12\r\nConnection: close\r\n\r\nhello world\n"
|
34
|
+
close_connection true
|
35
|
+
end
|
36
|
+
headers.clear
|
37
|
+
end
|
38
|
+
data.string.clear
|
39
|
+
# end
|
40
|
+
end
|
41
|
+
|
42
|
+
def unbind
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Note that this will block current thread.
|
47
|
+
EventMachine.run {
|
48
|
+
trap("TERM") { EventMachine.stop_event_loop }
|
49
|
+
trap("INT") { EventMachine.stop_event_loop }
|
50
|
+
EventMachine.start_server "127.0.0.1", 3000, MiniServer
|
51
|
+
}
|
52
|
+
|
53
|
+
|
54
|
+
# ab -n 10000 -c 200 -k http://127.0.0.1:3000/ctrl
|
55
|
+
# ~/ruby/wrk/wrk -c400 -d10 -t12 http://localhost:3000/ctrl
|
56
|
+
|
data/bin/http_test
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'pathname'
|
4
|
+
Root ||= Pathname.new(File.dirname(__FILE__)).expand_path
|
5
|
+
Dir.chdir Root.join('..').to_s
|
6
|
+
|
7
|
+
require "bundler/setup"
|
8
|
+
require "iodine"
|
9
|
+
require 'stringio'
|
10
|
+
|
11
|
+
# ab -n 10000 -c 200 -k http://127.0.0.1:3000/
|
12
|
+
# ab -n 10000 -c 200 -k http://localhost:3000/
|
13
|
+
# ~/ruby/wrk/wrk -c400 -d10 -t12 http://localhost:3000/
|
14
|
+
|
15
|
+
|
16
|
+
|
17
|
+
class MiniServer < Iodine::SSLProtocol
|
18
|
+
def on_open
|
19
|
+
@headers = {}
|
20
|
+
set_timeout 1
|
21
|
+
end
|
22
|
+
|
23
|
+
def on_message data
|
24
|
+
data = ::StringIO.new data
|
25
|
+
l = nil
|
26
|
+
headers = @headers
|
27
|
+
while (l = data.gets)
|
28
|
+
unless l =~ /^[\r]?\n/
|
29
|
+
if l.include? ':'
|
30
|
+
l = l.strip.downcase.split(':', 2)
|
31
|
+
headers[l[0]] = l[1]
|
32
|
+
else
|
33
|
+
headers[:method], headers[:query], headers[:version] = l.strip.split(/[\s]+/, 3)
|
34
|
+
end
|
35
|
+
next
|
36
|
+
end
|
37
|
+
if headers['connection'] =~ /keep/i || headers[:version] =~ /1\.1/
|
38
|
+
write "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 12\r\nConnection: keep-alive\r\nKeep-Alive: 5\r\n\r\nhello world\n"
|
39
|
+
else
|
40
|
+
write "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 12\r\nConnection: close\r\n\r\nhello world\n"
|
41
|
+
close
|
42
|
+
end
|
43
|
+
headers.clear
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
Iodine.protocol = MiniServer
|
data/bin/setup
ADDED
data/iodine.gemspec
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'iodine/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "iodine"
|
8
|
+
spec.version = Iodine::VERSION
|
9
|
+
spec.authors = ["Boaz Segev"]
|
10
|
+
spec.email = ["Boaz@2be.co.il"]
|
11
|
+
|
12
|
+
spec.summary = %q{ IOdine makes writing evented server applications easy to write. }
|
13
|
+
spec.description = %q{ IOdine is a super easy way to write network services and tasking scripts.}
|
14
|
+
spec.homepage = "https://github.com/boazsegev/iodine"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
# Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or
|
18
|
+
# delete this section to allow pushing this gem to any host.
|
19
|
+
if spec.respond_to?(:metadata)
|
20
|
+
spec.metadata['allowed_push_host'] = "https://rubygems.org"
|
21
|
+
else
|
22
|
+
raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
|
23
|
+
end
|
24
|
+
|
25
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
26
|
+
spec.bindir = "exe"
|
27
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
28
|
+
spec.require_paths = ["lib"]
|
29
|
+
|
30
|
+
spec.add_development_dependency "bundler", "~> 1.10"
|
31
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
32
|
+
spec.add_development_dependency "minitest"
|
33
|
+
end
|
data/lib/iodine.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require "logger"
|
2
|
+
require 'socket'
|
3
|
+
|
4
|
+
|
5
|
+
module Iodine
|
6
|
+
extend self
|
7
|
+
end
|
8
|
+
|
9
|
+
|
10
|
+
require "iodine/version"
|
11
|
+
require "iodine/settings"
|
12
|
+
require "iodine/logging"
|
13
|
+
require "iodine/core"
|
14
|
+
require "iodine/timers"
|
15
|
+
require "iodine/protocol"
|
16
|
+
require "iodine/ssl_protocol"
|
17
|
+
require "iodine/io"
|
data/lib/iodine/core.rb
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
module Iodine
|
2
|
+
public
|
3
|
+
|
4
|
+
#######################
|
5
|
+
## Events
|
6
|
+
|
7
|
+
# Accepts a block and runs it asynchronously. This method runs asynchronously and returns immediately.
|
8
|
+
#
|
9
|
+
# use:
|
10
|
+
#
|
11
|
+
# GReactor.run_async(arg1, arg2, arg3 ...) { |arg1, arg2, arg3...| do_something }
|
12
|
+
#
|
13
|
+
# the block will be run within the current context, allowing access to current methods and variables.
|
14
|
+
#
|
15
|
+
# @return [GReactor] always returns the reactor object.
|
16
|
+
def run *args, &block
|
17
|
+
queue block, args
|
18
|
+
end
|
19
|
+
alias :run_async :run
|
20
|
+
|
21
|
+
# This method runs an object's method asynchronously and returns immediately. This method will also run an optional callback if a block is supplied.
|
22
|
+
#
|
23
|
+
# This method accepts:
|
24
|
+
# object:: an object who's method will be called.
|
25
|
+
# method:: the method's name to be called. type: Symbol.
|
26
|
+
# *args:: any arguments to be passed to the method.
|
27
|
+
# block (optional):: If a block is supplied, it will be used as a callback and the method's return value will be passed on to the block.
|
28
|
+
#
|
29
|
+
# @return [GReactor] always returns the reactor object.
|
30
|
+
def callback object, method_name, *args, &block
|
31
|
+
block ? queue(@callback_proc, [object.method(method_name), args, block]) : queue(object.method(method_name), args)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Adds a job OR a block to the queue. {GReactor.run_async} and {GReactor.callback} extend this core method.
|
35
|
+
#
|
36
|
+
# This method accepts two possible arguments:
|
37
|
+
# job:: An object that answers to `call`, usually a Proc or Lambda.
|
38
|
+
# args:: (optional) An Array of arguments to be passed on to the executed method.
|
39
|
+
#
|
40
|
+
# @return [GReactor] always returns the reactor object.
|
41
|
+
#
|
42
|
+
# The callback will NOT be called if the executed job failed (raised an exception).
|
43
|
+
# @see .run_async
|
44
|
+
#
|
45
|
+
# @see .callback
|
46
|
+
def queue job, args = nil
|
47
|
+
@queue << [job, args]
|
48
|
+
self
|
49
|
+
end
|
50
|
+
|
51
|
+
# Adds a shutdown tasks. These tasks should be executed in order of creation.
|
52
|
+
def on_shutdown *args, &block
|
53
|
+
@shutdown_queue << [block, args]
|
54
|
+
self
|
55
|
+
end
|
56
|
+
|
57
|
+
protected
|
58
|
+
|
59
|
+
@queue = Queue.new
|
60
|
+
@shutdown_queue = Queue.new
|
61
|
+
@stop = true
|
62
|
+
@done = false
|
63
|
+
@logger = Logger.new(STDOUT)
|
64
|
+
@thread_count = 1
|
65
|
+
@ios = {}
|
66
|
+
@io_in = Queue.new
|
67
|
+
@io_out = Queue.new
|
68
|
+
@shutdown_mutex = Mutex.new
|
69
|
+
@servers = {}
|
70
|
+
|
71
|
+
|
72
|
+
def cycle
|
73
|
+
work until @stop && @queue.empty?
|
74
|
+
@shutdown_mutex.synchronize { shutdown }
|
75
|
+
work until @queue.empty?
|
76
|
+
run { true }
|
77
|
+
end
|
78
|
+
|
79
|
+
def work
|
80
|
+
job = @queue && @queue.pop
|
81
|
+
if job && job[0]
|
82
|
+
begin
|
83
|
+
job[0].call *job[1]
|
84
|
+
rescue => e
|
85
|
+
error e
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
Kernel.at_exit do
|
91
|
+
threads = []
|
92
|
+
@thread_count.times { threads << Thread.new { cycle } }
|
93
|
+
unless @stop
|
94
|
+
catch(:stop) { sleep }
|
95
|
+
@logger << "\nShutting down Iodine. Setting shutdown timeout to 30 seconds.\n"
|
96
|
+
@stop = true
|
97
|
+
# setup exit timeout.
|
98
|
+
threads.each {|t| Thread.new {sleep 30; t.kill; t.kill } }
|
99
|
+
end
|
100
|
+
threads.each {|t| t.join rescue true }
|
101
|
+
end
|
102
|
+
|
103
|
+
def shutdown
|
104
|
+
return if @done
|
105
|
+
@stop = @done = true
|
106
|
+
arr = []
|
107
|
+
arr.push @shutdown_queue.pop until @shutdown_queue.empty?
|
108
|
+
@queue.push arr.pop while arr[0]
|
109
|
+
@thread_count.times { run { true } }
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
data/lib/iodine/io.rb
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
module Iodine
|
2
|
+
|
3
|
+
public
|
4
|
+
|
5
|
+
# Gets the last time at which the IO Reactor was last active (last "tick").
|
6
|
+
def time
|
7
|
+
@time
|
8
|
+
end
|
9
|
+
|
10
|
+
# replaces an IO's protocol object.
|
11
|
+
#
|
12
|
+
# accepts:
|
13
|
+
# io:: the raw IO object.
|
14
|
+
# protocol:: a protocol instance - should be a Protocol or SSLProtocol (subclass) instance. type will NOT be checked - but Iodine could break if there is a type mismatch.
|
15
|
+
#
|
16
|
+
def switch_protocol *args
|
17
|
+
@io_in << args
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
protected
|
22
|
+
|
23
|
+
@port = (ARGV.index('-p') && ARGV[ARGV.index('-p') + 1]) || ENV['PORT'] || 3000
|
24
|
+
@bind = (ARGV.index('-ip') && ARGV[ARGV.index('-ip') + 1]) || ENV['IP'] || "0.0.0.0"
|
25
|
+
@protocol = nil
|
26
|
+
@ssl_context = nil
|
27
|
+
@time = Time.now
|
28
|
+
|
29
|
+
@timeout_proc = Proc.new {|prot| prot.timeout?(@time) }
|
30
|
+
@status_loop = Proc.new {|io| @io_out << io if io.closed? || !(io.stat.readable? rescue false) }
|
31
|
+
@close_callback = Proc.new {|prot| prot.on_close if prot }
|
32
|
+
REACTOR = Proc.new do
|
33
|
+
if @queue.empty?
|
34
|
+
#clear any closed IO objects.
|
35
|
+
@time = Time.now
|
36
|
+
@ios.keys.each &@status_loop
|
37
|
+
@ios.values.each &@timeout_proc
|
38
|
+
until @io_in.empty?
|
39
|
+
n_io = @io_in.pop
|
40
|
+
@ios[n_io[0]] = n_io[1]
|
41
|
+
end
|
42
|
+
until @io_out.empty?
|
43
|
+
o_io = @io_out.pop
|
44
|
+
o_io.close unless o_io.closed?
|
45
|
+
queue @close_callback, @ios.delete(o_io)
|
46
|
+
end
|
47
|
+
# react to IO events
|
48
|
+
begin
|
49
|
+
r = IO.select(@ios.keys, nil, nil, 0.15)
|
50
|
+
r[0].each {|io| queue @ios[io] } if r
|
51
|
+
rescue => e
|
52
|
+
|
53
|
+
end
|
54
|
+
unless @stop && @queue.empty?
|
55
|
+
# @ios.values.each &@timeout_loop
|
56
|
+
@check_timers && queue(@check_timers)
|
57
|
+
queue REACTOR
|
58
|
+
end
|
59
|
+
else
|
60
|
+
queue REACTOR
|
61
|
+
end
|
62
|
+
end
|
63
|
+
# internal helper methods and classes.
|
64
|
+
module Base
|
65
|
+
# the server listener Protocol.
|
66
|
+
class Listener < ::Iodine::Protocol
|
67
|
+
def on_open
|
68
|
+
@protocol = Iodine.protocol
|
69
|
+
end
|
70
|
+
def call
|
71
|
+
begin
|
72
|
+
n_io = nil
|
73
|
+
loop do
|
74
|
+
n_io = @io.accept_nonblock
|
75
|
+
@protocol.accept(n_io)
|
76
|
+
end
|
77
|
+
rescue Errno::EWOULDBLOCK => e
|
78
|
+
|
79
|
+
rescue OpenSSL::SSL::SSLError => e
|
80
|
+
warn "SSL Error - Self-signed Certificate?".freeze
|
81
|
+
n_io.close if n_io && !n_io.closed?
|
82
|
+
rescue => e
|
83
|
+
n_io.close if n_io && !n_io.closed?
|
84
|
+
@stop = true
|
85
|
+
raise e
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
########
|
92
|
+
## remember to set traps (once) when 'listen' is called.
|
93
|
+
run do
|
94
|
+
next unless @protocol
|
95
|
+
|
96
|
+
shut_down_proc = Proc.new {|protocol| protocol.on_shutdown ; protocol.close }
|
97
|
+
on_shutdown do
|
98
|
+
@logger << "Stopping to listen on port #{@port} and shutting down.\n"
|
99
|
+
@server.close unless @server.closed?
|
100
|
+
@ios.values.each {|p| queue shut_down_proc, p }
|
101
|
+
end
|
102
|
+
@server = ::TCPServer.new(@bind, @port)
|
103
|
+
::Iodine::Base::Listener.accept(@server)
|
104
|
+
@logger << "Iodine #{VERSION} is listening on port #{@port}\n"
|
105
|
+
old_int_trap = trap('INT') { throw :stop; trap('INT', old_int_trap) if old_int_trap }
|
106
|
+
old_term_trap = trap('TERM') { throw :stop; trap('TERM', old_term_trap) if old_term_trap }
|
107
|
+
@logger << "Press ^C to stop the server.\n"
|
108
|
+
queue REACTOR
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Iodine
|
2
|
+
public
|
3
|
+
|
4
|
+
# Gets the logging object and allows you to call logging methods (i.e. `Iodine.log.info "Running"`).
|
5
|
+
def logger log = nil
|
6
|
+
@logger
|
7
|
+
end
|
8
|
+
|
9
|
+
# logs info
|
10
|
+
# @return [String, Exception, Object] always returns the Object sent to the log.
|
11
|
+
def info data, &block
|
12
|
+
@logger.info data, &block if @logger
|
13
|
+
data
|
14
|
+
end
|
15
|
+
# logs debug info
|
16
|
+
# @return [String, Exception, Object] always returns the Object sent to the log.
|
17
|
+
def debug data, &block
|
18
|
+
@logger.debug data, &block if @logger
|
19
|
+
data
|
20
|
+
end
|
21
|
+
# logs warning
|
22
|
+
# @return [String, Exception, Object] always returns the Object sent to the log.
|
23
|
+
def warn data, &block
|
24
|
+
@logger.warn data, &block if @logger
|
25
|
+
data
|
26
|
+
end
|
27
|
+
# logs errors
|
28
|
+
# @return [String, Exception, Object] always returns the Object sent to the log.
|
29
|
+
def error data, &block
|
30
|
+
@logger.error data, &block if @logger
|
31
|
+
data
|
32
|
+
end
|
33
|
+
# logs a fatal error
|
34
|
+
# @return [String, Exception, Object] always returns the Object sent to the log.
|
35
|
+
def fatal data, &block
|
36
|
+
@logger.fatal data, &block if @logger
|
37
|
+
data
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
protected
|
42
|
+
|
43
|
+
@logger = Logger.new(STDOUT)
|
44
|
+
|
45
|
+
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
module Iodine
|
2
|
+
|
3
|
+
# This is the Basic Iodine server unit - a network protocol.
|
4
|
+
#
|
5
|
+
# A new protocol instance will be created for every network connection.
|
6
|
+
#
|
7
|
+
# The recommended use is to inherit this class (or {SSLProtocol}) and override any of the following:
|
8
|
+
# on_open:: called whenever the Protocol is initialized. Override this to initialize the Protocol object.
|
9
|
+
# on_message(data):: called whenever data is received from the IO. Override this to implement the actual network protocol.
|
10
|
+
# on_close:: called AFTER the Protocol's IO is closed.
|
11
|
+
# on_shutdown:: called when the server's shutdown process had started and BEFORE the Protocol's IO is closed. It allows graceful shutdown for network protocols.
|
12
|
+
# ping::
|
13
|
+
#
|
14
|
+
# Once the network protocol class was created, remember to tell Iodine about it:
|
15
|
+
# class MyProtocol << Iodine::Protocol
|
16
|
+
# # your code here
|
17
|
+
# end
|
18
|
+
# # tell Iodine
|
19
|
+
# Iodine.protocol = MyProtocol
|
20
|
+
#
|
21
|
+
class Protocol
|
22
|
+
|
23
|
+
# returns the raw IO object. Using one of the Protocol methods {#write}, {#read}, {#close} is prefered over direct access.
|
24
|
+
attr_reader :io
|
25
|
+
|
26
|
+
# Sets the timeout in seconds for IO activity (set timeout within {#on_open}).
|
27
|
+
#
|
28
|
+
# After timeout is reached, {#ping} will be closed. The connection will be closed if {#ping} returns `false` or `nil`.
|
29
|
+
def set_timeout seconds
|
30
|
+
@timeout = seconds
|
31
|
+
end
|
32
|
+
|
33
|
+
# This method is called whenever the Protocol is initialized - i.e.:
|
34
|
+
# a new connection is established or an old connection switches to this protocol.
|
35
|
+
def on_open
|
36
|
+
end
|
37
|
+
# This method is called whenever data is received from the IO.
|
38
|
+
def on_message data
|
39
|
+
end
|
40
|
+
|
41
|
+
# This method is called AFTER the Protocol's IO is closed - it will only be called once.
|
42
|
+
def on_close
|
43
|
+
end
|
44
|
+
|
45
|
+
# This method is called when the server's shutdown process had started and BEFORE the Protocol's IO is closed. It allows graceful shutdown for network protocols.
|
46
|
+
def on_shutdown
|
47
|
+
end
|
48
|
+
|
49
|
+
# This method is called whenever a timeout has occurred. Either implement a ping or return `false` to disconnect.
|
50
|
+
#
|
51
|
+
# A `false` or `nil` return value will cause disconnection
|
52
|
+
def ping
|
53
|
+
false
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
# Closes the IO object.
|
58
|
+
# @return [nil]
|
59
|
+
def close
|
60
|
+
@io.close unless @io.closed?
|
61
|
+
nil
|
62
|
+
end
|
63
|
+
alias :disconnect :close
|
64
|
+
|
65
|
+
# reads from the IO up to the specified number of bytes (defaults to ~2Mb).
|
66
|
+
def read size = 2_097_152
|
67
|
+
touch
|
68
|
+
@io.recv_nonblock( size )
|
69
|
+
rescue => e
|
70
|
+
nil
|
71
|
+
end
|
72
|
+
|
73
|
+
# this method, writes data to the socket / io object.
|
74
|
+
def write data
|
75
|
+
begin
|
76
|
+
@send_locker.synchronize do
|
77
|
+
r = @io.write data
|
78
|
+
touch
|
79
|
+
r
|
80
|
+
end
|
81
|
+
rescue => e
|
82
|
+
# GReactor.warn e
|
83
|
+
close
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
|
88
|
+
# This method allows switiching the IO's protocol that will be used the NEXT time
|
89
|
+
# Iodine receives data using this protocol's IO.
|
90
|
+
#
|
91
|
+
# Switing protocols bypasses the {#on_close} method. Override this method for any cleanup needed (if at any),
|
92
|
+
# but remember to call `super` for the actual protocol switching implementation.
|
93
|
+
def switch_protocol new_protocol
|
94
|
+
Iodine.switch_protocol @io, new_protocol
|
95
|
+
end
|
96
|
+
|
97
|
+
#################
|
98
|
+
## the following are Iodine's "system" methods, used internally. Don't override.
|
99
|
+
|
100
|
+
|
101
|
+
# This method is used by Iodine to initialized the Protocol.
|
102
|
+
#
|
103
|
+
# Normally you won't need to override this method. Override {#on_open} instead.
|
104
|
+
def initialize io
|
105
|
+
@timeout ||= nil
|
106
|
+
@send_locker = Mutex.new
|
107
|
+
@locker = Mutex.new
|
108
|
+
@io = io
|
109
|
+
switch_protocol self
|
110
|
+
touch
|
111
|
+
on_open
|
112
|
+
end
|
113
|
+
|
114
|
+
# Called by Iodine whenever there is data in the IO's read buffer.
|
115
|
+
#
|
116
|
+
# Normally you won't need to override this method. Override {#on_message} instead.
|
117
|
+
def call
|
118
|
+
return unless @locker.try_lock
|
119
|
+
begin
|
120
|
+
data = read
|
121
|
+
if data
|
122
|
+
on_message(data)
|
123
|
+
data.clear
|
124
|
+
end
|
125
|
+
ensure
|
126
|
+
@locker.unlock
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
|
131
|
+
# This method is used by Iodine to ask whether a timeout has occured.
|
132
|
+
#
|
133
|
+
# Normally you won't need to override this method. See {#ping}
|
134
|
+
def timeout? time
|
135
|
+
(ping || close) if @timeout && !@send_locker.locked? && ( (time - @last_active) > @timeout )
|
136
|
+
end
|
137
|
+
|
138
|
+
|
139
|
+
|
140
|
+
# This method is used by Iodine to create the IO handler whenever a new connection is established.
|
141
|
+
#
|
142
|
+
# Normally you won't need to override this method.
|
143
|
+
def self.accept io
|
144
|
+
self.new(io)
|
145
|
+
end
|
146
|
+
|
147
|
+
protected
|
148
|
+
|
149
|
+
# This methos updates the timeout "watch", signifying the IO was active.
|
150
|
+
def touch
|
151
|
+
@last_active = Iodine.time
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
module Iodine
|
2
|
+
public
|
3
|
+
|
4
|
+
#######################
|
5
|
+
## Settings - methods that change the way Iodine behaves should go here.
|
6
|
+
|
7
|
+
# Sets the logging object, which needs to act like `Logger`. The default logger is `Logger.new(STDOUT)`.
|
8
|
+
def logger= obj
|
9
|
+
@logger = obj
|
10
|
+
end
|
11
|
+
|
12
|
+
# Sets the number of threads in the thread pool used for executing the tasks. Defaults to 1 thread.
|
13
|
+
def threads= count
|
14
|
+
@thread_count = count
|
15
|
+
end
|
16
|
+
|
17
|
+
# Sets the server port. Defaults to the runtime `-p` argument, or the ENV['PORT'] or 3000 (in this order).
|
18
|
+
def port= port
|
19
|
+
@port = port
|
20
|
+
end
|
21
|
+
# Sets the IP binding address. Defaults to the runtime `-ip` argument, or the ENV['IP'] or 0.0.0.0 (in this order).
|
22
|
+
def bind= address
|
23
|
+
@bind = address
|
24
|
+
end
|
25
|
+
|
26
|
+
# Sets the Protocol the Iodine Server will use. Should be a child of Protocol or SSLProtocol. Defaults to nil (no server).
|
27
|
+
def protocol= protocol
|
28
|
+
@stop = protocol ? false : true
|
29
|
+
@protocol = protocol
|
30
|
+
end
|
31
|
+
# Returns the cutrently active Iodine protocol (if exists).
|
32
|
+
def protocol
|
33
|
+
@protocol
|
34
|
+
end
|
35
|
+
|
36
|
+
# Sets the SSL Context to be used when using an SSLProtocol. Defaults to a self signed certificate.
|
37
|
+
def ssl_context= context
|
38
|
+
@ssl_context = context
|
39
|
+
end
|
40
|
+
# Gets the SSL Context to be used when using an SSLProtocol.
|
41
|
+
def ssl_context
|
42
|
+
@ssl_context ||= init_ssl_context
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
protected
|
47
|
+
|
48
|
+
#initializes a default SSLContext
|
49
|
+
def init_ssl_context
|
50
|
+
ssl_context = OpenSSL::SSL::SSLContext.new
|
51
|
+
ssl_context.set_params verify_mode: OpenSSL::SSL::VERIFY_NONE
|
52
|
+
ssl_context.cert_store = OpenSSL::X509::Store.new
|
53
|
+
ssl_context.cert_store.set_default_paths
|
54
|
+
ssl_context.session_cache_mode = OpenSSL::SSL::SSLContext::SESSION_CACHE_NO_INTERNAL #SESSION_CACHE_OFF
|
55
|
+
ssl_context.cert, ssl_context.key = create_cert
|
56
|
+
ssl_context
|
57
|
+
end
|
58
|
+
|
59
|
+
#creates a self-signed certificate
|
60
|
+
def create_cert bits=2048, cn=nil, comment='a self signed certificate for when we only need encryption and no more.'
|
61
|
+
unless cn
|
62
|
+
host_name = Socket::gethostbyname(Socket::gethostname)[0].split('.')
|
63
|
+
cn = ''
|
64
|
+
host_name.each {|n| cn << "/DC=#{n}"}
|
65
|
+
cn << "/CN=Iodine.#{host_name.join('.')}"
|
66
|
+
end
|
67
|
+
# cn ||= "CN=#{Socket::gethostbyname(Socket::gethostname)[0] rescue Socket::gethostname}"
|
68
|
+
|
69
|
+
time = Time.now
|
70
|
+
rsa = OpenSSL::PKey::RSA.new(bits)
|
71
|
+
cert = OpenSSL::X509::Certificate.new
|
72
|
+
cert.version = 2
|
73
|
+
cert.serial = 1
|
74
|
+
name = OpenSSL::X509::Name.parse(cn)
|
75
|
+
cert.subject = name
|
76
|
+
cert.issuer = name
|
77
|
+
cert.not_before = time
|
78
|
+
cert.not_after = time + (365*24*60*60)
|
79
|
+
cert.public_key = rsa.public_key
|
80
|
+
|
81
|
+
ef = OpenSSL::X509::ExtensionFactory.new(nil,cert)
|
82
|
+
ef.issuer_certificate = cert
|
83
|
+
cert.extensions = [
|
84
|
+
ef.create_extension("basicConstraints","CA:FALSE"),
|
85
|
+
ef.create_extension("keyUsage", "keyEncipherment"),
|
86
|
+
ef.create_extension("subjectKeyIdentifier", "hash"),
|
87
|
+
ef.create_extension("extendedKeyUsage", "serverAuth"),
|
88
|
+
ef.create_extension("nsComment", comment),
|
89
|
+
]
|
90
|
+
aki = ef.create_extension("authorityKeyIdentifier",
|
91
|
+
"keyid:always,issuer:always")
|
92
|
+
cert.add_extension(aki)
|
93
|
+
cert.sign(rsa, OpenSSL::Digest::SHA1.new)
|
94
|
+
|
95
|
+
return cert, rsa
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
module Iodine
|
2
|
+
|
3
|
+
# This class inherits from Protocol, but it includes some adjustments related to SSL connection handling.
|
4
|
+
class SSLProtocol < Protocol
|
5
|
+
|
6
|
+
# This is a mini-protocol used only to implement the SSL Handshake in a non-blocking manner,
|
7
|
+
# allowing for a hardcoded timeout (which you can monkey patch) of 3 seconds.
|
8
|
+
class SSLWait < Protocol
|
9
|
+
TIMEOUT = 3 # hardcoded SSL/TLS handshake timeout
|
10
|
+
def on_open
|
11
|
+
timeout = TIMEOUT
|
12
|
+
@ssl_socket = OpenSSL::SSL::SSLSocket.new(@io, Iodine.ssl_context)
|
13
|
+
@ssl_socket.sync_close = true
|
14
|
+
end
|
15
|
+
|
16
|
+
# atempt an SSL Handshale
|
17
|
+
def call
|
18
|
+
return if @locker.locked?
|
19
|
+
return unless @locker.try_lock
|
20
|
+
begin
|
21
|
+
@ssl_socket.accept_nonblock
|
22
|
+
@locker.unlock
|
23
|
+
rescue IO::WaitReadable, IO::WaitWritable
|
24
|
+
@locker.unlock
|
25
|
+
return
|
26
|
+
rescue OpenSSL::SSL::SSLError
|
27
|
+
@e = Exception.new "Self-signed Certificate?".freeze
|
28
|
+
@locker.unlock
|
29
|
+
return
|
30
|
+
rescue => e
|
31
|
+
Iodine.warn "SSL Handshake failed with: #{e.message}".freeze
|
32
|
+
@e = e
|
33
|
+
close
|
34
|
+
@locker.unlock
|
35
|
+
return
|
36
|
+
end
|
37
|
+
Iodine.protocol.new @ssl_socket
|
38
|
+
end
|
39
|
+
def on_close
|
40
|
+
# inform
|
41
|
+
Iodine.warn "SSL Handshake #{@e ? "failed with: #{@e.message} (#{@e.class.name})" : 'timed-out.'}".freeze
|
42
|
+
# the core @io is already closed, but let's make sure the SSL object is closed as well.
|
43
|
+
@ssl_socket.close unless @ssl_socket.closed?
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
attr_reader :ssl_socket
|
49
|
+
|
50
|
+
|
51
|
+
|
52
|
+
# reads from the IO up to the specified number of bytes (defaults to ~1Mb).
|
53
|
+
def read size = 1048576
|
54
|
+
@send_locker.synchronize do
|
55
|
+
data = ''
|
56
|
+
begin
|
57
|
+
(data << @ssl_socket.read_nonblock(size).to_s) until data.bytesize >= size
|
58
|
+
rescue OpenSSL::SSL::SSLErrorWaitReadable, IO::WaitReadable, IO::WaitWritable
|
59
|
+
|
60
|
+
rescue IOError
|
61
|
+
close
|
62
|
+
rescue => e
|
63
|
+
Iodine.warn "SSL Protocol read error: #{e.class.name} #{e.message} (closing connection)"
|
64
|
+
close
|
65
|
+
end
|
66
|
+
return false if data.to_s.empty?
|
67
|
+
touch
|
68
|
+
data
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def write data
|
73
|
+
begin
|
74
|
+
@send_locker.synchronize do
|
75
|
+
r = @ssl_socket.write data
|
76
|
+
touch
|
77
|
+
r
|
78
|
+
end
|
79
|
+
rescue => e
|
80
|
+
close
|
81
|
+
false
|
82
|
+
end
|
83
|
+
end
|
84
|
+
alias :send :write
|
85
|
+
alias :<< :write
|
86
|
+
|
87
|
+
def on_close
|
88
|
+
@ssl_socket.close unless @ssl_socket.closed?
|
89
|
+
super
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
|
94
|
+
|
95
|
+
|
96
|
+
# This method initializes the SSL Protocol
|
97
|
+
def initialize ssl_socket
|
98
|
+
@ssl_socket = ssl_socket
|
99
|
+
super(ssl_socket.io)
|
100
|
+
end
|
101
|
+
|
102
|
+
def self.accept io
|
103
|
+
SSLWait.new(io)
|
104
|
+
end
|
105
|
+
|
106
|
+
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
module Iodine
|
2
|
+
|
3
|
+
#######################
|
4
|
+
## Timers
|
5
|
+
|
6
|
+
|
7
|
+
# Every timed event is a member of the TimedEvent class and responds to it's methods.
|
8
|
+
class TimedEvent
|
9
|
+
|
10
|
+
# Sets/gets how often a timed event repeats, in seconds.
|
11
|
+
attr_accessor :interval
|
12
|
+
# Sets/gets how many times a timed event repeats.
|
13
|
+
# If set to false or -1, the timed event will repead until the application quits.
|
14
|
+
attr_accessor :repeat_limit
|
15
|
+
|
16
|
+
# Initialize a timed event.
|
17
|
+
def initialize reactor, interval, repeat_limit = -1, args=[], job=nil
|
18
|
+
@interval = interval
|
19
|
+
@repeat_limit = repeat_limit ? repeat_limit.to_i : -1
|
20
|
+
@job = job || (Proc.new { stop! })
|
21
|
+
@next = Iodine.time + interval
|
22
|
+
args << self
|
23
|
+
@args = args
|
24
|
+
end
|
25
|
+
|
26
|
+
# stops a timed event.
|
27
|
+
# @return [GReactor::TimedEvent] returns the TimedEvent object.
|
28
|
+
def stop!
|
29
|
+
@repeat_limit = 0
|
30
|
+
self
|
31
|
+
end
|
32
|
+
|
33
|
+
# Returns true if the timer is finished.
|
34
|
+
#
|
35
|
+
# If the timed event is due, this method will also add the event to the queue.
|
36
|
+
# @return [true, false]
|
37
|
+
def done?
|
38
|
+
return false unless @next <= Iodine.time
|
39
|
+
return true if @repeat_limit == 0
|
40
|
+
@repeat_limit -= 1 if @repeat_limit.to_i > 0
|
41
|
+
Iodine.queue @job, @args
|
42
|
+
@next = Iodine.time + @interval
|
43
|
+
@repeat_limit == 0
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
public
|
48
|
+
|
49
|
+
# pushes a timed event to the timers's stack
|
50
|
+
#
|
51
|
+
# accepts:
|
52
|
+
# seconds:: the minimal amount of seconds to wait before calling the handler's `call` method.
|
53
|
+
# *arg:: any arguments that will be passed to the handler's `call` method.
|
54
|
+
# &block:: the block to execute.
|
55
|
+
#
|
56
|
+
# A block is required.
|
57
|
+
#
|
58
|
+
# On top of the arguments passed to the `run_after` method, the timer object will be passed as the last agrument to the receiving block.
|
59
|
+
#
|
60
|
+
# Timed event's time of execution is dependant on the workload and continuous uptime of the process (timed events AREN'T persistent).
|
61
|
+
#
|
62
|
+
# @return [GReactor::TimedEvent] returns the new TimedEvent object.
|
63
|
+
def run_after seconds, *args, &block
|
64
|
+
timed_job seconds, 1, args, block
|
65
|
+
end
|
66
|
+
|
67
|
+
# pushes a timed event to the timers's stack
|
68
|
+
#
|
69
|
+
# accepts:
|
70
|
+
# time:: the time at which the job should be executed.
|
71
|
+
# *arg:: any arguments that will be passed to the handler's `call` method.
|
72
|
+
# &block:: the block to execute.
|
73
|
+
#
|
74
|
+
# A block is required.
|
75
|
+
#
|
76
|
+
# On top of the arguments passed to the `run_after` method, the timer object will be passed as the last agrument to the receiving block.
|
77
|
+
#
|
78
|
+
# Timed event's time of execution is dependant on the workload and continuous uptime of the process (timed events AREN'T persistent).
|
79
|
+
#
|
80
|
+
# @return [GReactor::TimedEvent] returns the new TimedEvent object.
|
81
|
+
def run_at run_time, *args, &block
|
82
|
+
timed_job( (@time - run_time), 1, args, block)
|
83
|
+
end
|
84
|
+
# pushes a repeated timed event to the timers's stack
|
85
|
+
#
|
86
|
+
# accepts:
|
87
|
+
# seconds:: the minimal amount of seconds to wait before calling the handler's `call` method.
|
88
|
+
# limit:: the amount of times the event should repeat itself. The event will repeat every x amount of `seconds`. The event will repeat forever if limit is set to false.
|
89
|
+
# *arg:: any arguments that will be passed to the handler's `call` method.
|
90
|
+
# &block:: the block to execute.
|
91
|
+
#
|
92
|
+
# A block is required.
|
93
|
+
#
|
94
|
+
# On top of the arguments passed to the `run_after` method, the timer object will be passed as the last agrument to the receiving block.
|
95
|
+
#
|
96
|
+
# Timed event's time of execution is dependant on the workload and continuous uptime of the process (timed events AREN'T persistent unless you save and reload them yourself).
|
97
|
+
#
|
98
|
+
# @return [GReactor::TimedEvent] returns the new TimedEvent object.
|
99
|
+
def run_every seconds, limit = -1, *args, &block
|
100
|
+
timed_job seconds, limit, args, block
|
101
|
+
end
|
102
|
+
|
103
|
+
protected
|
104
|
+
@timer_locker = Mutex.new
|
105
|
+
@timers = []
|
106
|
+
|
107
|
+
# Creates a TimedEvent object and adds it to the Timers stack.
|
108
|
+
def timed_job seconds, limit = false, args = [], block = nil
|
109
|
+
@timer_locker.synchronize {@timers << TimedEvent.new(self, seconds, limit, args, block); @timers.last}
|
110
|
+
end
|
111
|
+
# cycles through timed jobs, executing and/or deleting them if their time has come.
|
112
|
+
@check_timers = Proc.new do
|
113
|
+
@timer_locker.synchronize { @timers.delete_if {|t| t.done? } }
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
metadata
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: iodine
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Boaz Segev
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-10-17 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.10'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.10'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description: " IOdine is a super easy way to write network services and tasking scripts."
|
56
|
+
email:
|
57
|
+
- Boaz@2be.co.il
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- ".gitignore"
|
63
|
+
- ".travis.yml"
|
64
|
+
- Gemfile
|
65
|
+
- LICENSE.txt
|
66
|
+
- README.md
|
67
|
+
- Rakefile
|
68
|
+
- bin/console
|
69
|
+
- bin/echo
|
70
|
+
- bin/em playground
|
71
|
+
- bin/http_test
|
72
|
+
- bin/setup
|
73
|
+
- iodine.gemspec
|
74
|
+
- lib/iodine.rb
|
75
|
+
- lib/iodine/core.rb
|
76
|
+
- lib/iodine/io.rb
|
77
|
+
- lib/iodine/logging.rb
|
78
|
+
- lib/iodine/protocol.rb
|
79
|
+
- lib/iodine/settings.rb
|
80
|
+
- lib/iodine/ssl_protocol.rb
|
81
|
+
- lib/iodine/timers.rb
|
82
|
+
- lib/iodine/version.rb
|
83
|
+
homepage: https://github.com/boazsegev/iodine
|
84
|
+
licenses:
|
85
|
+
- MIT
|
86
|
+
metadata:
|
87
|
+
allowed_push_host: https://rubygems.org
|
88
|
+
post_install_message:
|
89
|
+
rdoc_options: []
|
90
|
+
require_paths:
|
91
|
+
- lib
|
92
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
98
|
+
requirements:
|
99
|
+
- - ">="
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
requirements: []
|
103
|
+
rubyforge_project:
|
104
|
+
rubygems_version: 2.4.5.1
|
105
|
+
signing_key:
|
106
|
+
specification_version: 4
|
107
|
+
summary: IOdine makes writing evented server applications easy to write.
|
108
|
+
test_files: []
|