iodine 0.1.8 → 0.1.9
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 +4 -4
- data/CHANGELOG.md +10 -0
- data/README.md +3 -1
- data/lib/iodine.rb +11 -9
- data/lib/iodine/core.rb +17 -23
- data/lib/iodine/core_init.rb +143 -0
- data/lib/iodine/http/rack_support.rb +1 -4
- data/lib/iodine/http/websocket_client.rb +148 -111
- data/lib/iodine/io.rb +0 -88
- data/lib/iodine/ssl_connector.rb +3 -2
- data/lib/iodine/timers.rb +5 -12
- data/lib/iodine/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0b1ab6392f66ca513bb10c39e27ede18cf3588f8
|
4
|
+
data.tar.gz: 138fe6afa9327403a8811d036dccc5f3d91b66b9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 79797e0676d69aa78131e911b65d88fd1dd65305dc39d1ee62f4aecbeab96ae4d80ee021d01d337e78a84ddce23ac8a3dcebc03a9934df88f9168e1b4b2bb4e6
|
7
|
+
data.tar.gz: 16a7dfe4bfc94c03718ab69c40f8ea207a7f5ea57c986b118da08eb7afc144554266cfe42a88760a555cc69ea65ddb16081e9830cba8c74e1b3ca8fd1a7946d3
|
data/CHANGELOG.md
CHANGED
@@ -8,6 +8,16 @@ Please notice that this change log contains changes for upcoming releases as wel
|
|
8
8
|
|
9
9
|
***
|
10
10
|
|
11
|
+
Change log v.0.1.9
|
12
|
+
|
13
|
+
**Fix**: WebsocketClient connection renewal will now keep the same WebsocketClient instance object.
|
14
|
+
|
15
|
+
**Update** Creating a TimedEvent before Iodine starts running will automatically 'nudge' Iodine into "Task polling" mode, cycling until the user signals a stop.
|
16
|
+
|
17
|
+
**Update**: repeatedly calling `Iodine.force_start!` will now be ignored, as might have been expected. Once Iodine had started, `force_start!` cannot be called until Iodine had finished (and even than, Iodine might never be as fresh nor as young as it was).
|
18
|
+
|
19
|
+
***
|
20
|
+
|
11
21
|
Change log v.0.1.8
|
12
22
|
|
13
23
|
**Fix**: Websocket broadcasts are now correctly executed within the IO's mutex locker. This maintains the idea that only one thread at a time should be executing code on behald of any given Protocol object ("yes" to concurrency between objects but "no" to concurrency within objects).
|
data/README.md
CHANGED
@@ -62,7 +62,7 @@ In this mode, Iodine will continue running until all the tasks have completed an
|
|
62
62
|
|
63
63
|
This mode of operation is effective if want Iodine to periodically initiates new tasks, for instance if you cannot use `cron`.
|
64
64
|
|
65
|
-
To initiate this mode, simply set: `Iodine.protocol = :timers`
|
65
|
+
To initiate this mode, simply set: `Iodine.protocol = :timers` OR create a TimedEvent.
|
66
66
|
|
67
67
|
In example:
|
68
68
|
|
@@ -260,6 +260,8 @@ Iodine applies this concept when running in Server mode by locking the Protocol
|
|
260
260
|
|
261
261
|
For instance, in Iodine's implementation for the Websocket protocol: Websocket messages to different connections can run concurrently, however multiple messages to the same connection are only executed one at a time, maintaining their order (lately a fix in version 0.1.8 made sure that also websocket broadcasting will be executed within the Protocol lock, preventing concurrency within the same connection).
|
262
262
|
|
263
|
+
The exception to the rule is the `ping` implementation. Your protocol's `ping` method will execute in parallel with other parts of your protocol's code. Pinging is a machanism that is often time sensitive and is required to maintain an open connection. For this reason, if your code is working hard on a long task, a ping will still occure automatically in the background and offset any connection timeout.
|
264
|
+
|
263
265
|
## Development
|
264
266
|
|
265
267
|
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.
|
data/lib/iodine.rb
CHANGED
@@ -1,7 +1,17 @@
|
|
1
1
|
require 'logger'
|
2
2
|
require 'socket'
|
3
3
|
require 'openssl'
|
4
|
-
|
4
|
+
|
5
|
+
require "iodine/version"
|
6
|
+
require "iodine/settings"
|
7
|
+
require "iodine/logging"
|
8
|
+
require "iodine/core"
|
9
|
+
require "iodine/timers"
|
10
|
+
require "iodine/protocol"
|
11
|
+
require "iodine/ssl_connector"
|
12
|
+
require "iodine/io"
|
13
|
+
require "iodine/core_init"
|
14
|
+
|
5
15
|
|
6
16
|
|
7
17
|
# Iodine is an easy Object-Oriented library for writing network applications (servers) with your own
|
@@ -57,13 +67,5 @@ module Iodine
|
|
57
67
|
end
|
58
68
|
|
59
69
|
|
60
|
-
require "iodine/version"
|
61
|
-
require "iodine/settings"
|
62
|
-
require "iodine/logging"
|
63
|
-
require "iodine/core"
|
64
|
-
require "iodine/timers"
|
65
|
-
require "iodine/protocol"
|
66
|
-
require "iodine/ssl_connector"
|
67
|
-
require "iodine/io"
|
68
70
|
|
69
71
|
# require 'iodine/http'
|
data/lib/iodine/core.rb
CHANGED
@@ -31,29 +31,26 @@ module Iodine
|
|
31
31
|
nil
|
32
32
|
end
|
33
33
|
|
34
|
-
# forces Iodine to start prematurely and asyncronously. This might
|
34
|
+
# forces Iodine to start prematurely and asyncronously. This might cause Iodine's exit sequence to end abruptly, depending how the hosting application behaves.
|
35
|
+
#
|
36
|
+
# calling this method repeatedly will be ignored unless Iodine's threads have all died.
|
37
|
+
#
|
38
|
+
# @return [Iodine] the method will allways return `self` (Iodine).
|
35
39
|
def force_start!
|
36
|
-
|
40
|
+
return self if @force_running
|
41
|
+
@force_running = true
|
42
|
+
thread = Thread.new do
|
43
|
+
startup true
|
44
|
+
@force_running = false
|
45
|
+
initialize_tasks
|
46
|
+
@stop = false if @protocol
|
47
|
+
end
|
37
48
|
Kernel.at_exit {thread.raise("stop"); thread.join}
|
38
49
|
self
|
39
50
|
end
|
40
51
|
|
41
52
|
protected
|
42
53
|
|
43
|
-
@queue = Queue.new
|
44
|
-
@shutdown_queue = Queue.new
|
45
|
-
@stop = true
|
46
|
-
@done = false
|
47
|
-
@logger = Logger.new(STDOUT)
|
48
|
-
@spawn_count = 1
|
49
|
-
@thread_count = nil
|
50
|
-
@ios = {}
|
51
|
-
@io_in = Queue.new
|
52
|
-
@io_out = Queue.new
|
53
|
-
@shutdown_mutex = Mutex.new
|
54
|
-
@servers = {}
|
55
|
-
|
56
|
-
|
57
54
|
def cycle
|
58
55
|
work until @stop && @queue.empty?
|
59
56
|
@shutdown_mutex.synchronize { shutdown }
|
@@ -73,17 +70,18 @@ module Iodine
|
|
73
70
|
end
|
74
71
|
|
75
72
|
def startup use_rescue = false, hide_message = false
|
73
|
+
@force_running = true
|
76
74
|
threads = []
|
77
75
|
(@thread_count ||= 1).times { threads << Thread.new { cycle } }
|
78
76
|
unless @stop
|
79
77
|
if use_rescue
|
80
78
|
sleep rescue true
|
81
79
|
else
|
82
|
-
old_int_trap = trap('INT') { throw :stop;
|
83
|
-
old_term_trap = trap('TERM') { throw :stop;
|
80
|
+
old_int_trap = trap('INT') { throw :stop; old_int_trap.respond_to?(:call) && old_int_trap.call }
|
81
|
+
old_term_trap = trap('TERM') { throw :stop; old_term_trap.respond_to?(:call) && old_term_trap.call }
|
84
82
|
catch(:stop) { sleep }
|
85
83
|
end
|
86
|
-
log "\nShutting down Iodine. Setting shutdown timeout to 25 seconds.\n" unless hide_message
|
84
|
+
log "\nShutting down #{self == Iodine ? 'Iodine' : "#{self.name} (Iodine)"}. Setting shutdown timeout to 25 seconds.\n" unless hide_message
|
87
85
|
@stop = true
|
88
86
|
# setup exit timeout.
|
89
87
|
threads.each {|t| Thread.new {sleep 25; t.kill; t.kill } }
|
@@ -91,10 +89,6 @@ module Iodine
|
|
91
89
|
threads.each {|t| t.join rescue true }
|
92
90
|
end
|
93
91
|
|
94
|
-
Kernel.at_exit do
|
95
|
-
startup
|
96
|
-
end
|
97
|
-
|
98
92
|
# performed once - the shutdown sequence.
|
99
93
|
def shutdown
|
100
94
|
return if @done
|
@@ -0,0 +1,143 @@
|
|
1
|
+
module Iodine
|
2
|
+
protected
|
3
|
+
|
4
|
+
def extended base
|
5
|
+
base.instance_exec do
|
6
|
+
initialize_core
|
7
|
+
initialize_io
|
8
|
+
initialize_timers
|
9
|
+
initialize_tasks
|
10
|
+
end
|
11
|
+
base.protocol = :cycle unless base == Iodine
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize_core
|
15
|
+
# initializes all the core components
|
16
|
+
# referenced in `core.rb`
|
17
|
+
@queue = Queue.new
|
18
|
+
@shutdown_queue = Queue.new
|
19
|
+
@stop = true
|
20
|
+
@done = false
|
21
|
+
@logger = Logger.new(STDOUT)
|
22
|
+
@spawn_count = 1
|
23
|
+
@thread_count = nil
|
24
|
+
@ios = {}
|
25
|
+
@io_in = Queue.new
|
26
|
+
@io_out = Queue.new
|
27
|
+
@shutdown_mutex = Mutex.new
|
28
|
+
@servers = {}
|
29
|
+
Kernel.at_exit do
|
30
|
+
if self == Iodine
|
31
|
+
startup
|
32
|
+
else
|
33
|
+
Iodine.protocol ||= :cycle if @timers.any? || @protocol
|
34
|
+
thread = Thread.new { startup true }
|
35
|
+
Iodine.on_shutdown { thread.raise 'stop' ; thread.join }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def initialize_io
|
41
|
+
# initializes all the IO components
|
42
|
+
# referenced in `io.rb`
|
43
|
+
@port = ((ARGV.index('-p') && ARGV[ARGV.index('-p') + 1]) || ENV['PORT'] || 3000).to_i
|
44
|
+
@bind = (ARGV.index('-ip') && ARGV[ARGV.index('-ip') + 1]) || ENV['IP'] || "0.0.0.0"
|
45
|
+
@ssl = (ARGV.index('ssl') && true) || (@port == 443)
|
46
|
+
@protocol = nil
|
47
|
+
@ssl_context = nil
|
48
|
+
@ssl_protocols = {}
|
49
|
+
@time = Time.now
|
50
|
+
|
51
|
+
@timeout_proc = Proc.new {|prot| prot.timeout?(@time) }
|
52
|
+
@status_loop = Proc.new {|io| @io_out << io if io.closed? || !(io.stat.readable? rescue false) }
|
53
|
+
@close_callback = Proc.new {|prot| prot.on_close if prot }
|
54
|
+
@reactor = [ (Proc.new do
|
55
|
+
if @queue.empty?
|
56
|
+
#clear any closed IO objects.
|
57
|
+
@time = Time.now
|
58
|
+
@ios.keys.each &@status_loop
|
59
|
+
@ios.values.each &@timeout_proc
|
60
|
+
until @io_in.empty?
|
61
|
+
n_io = @io_in.pop
|
62
|
+
@ios[n_io[0]] = n_io[1]
|
63
|
+
end
|
64
|
+
until @io_out.empty?
|
65
|
+
o_io = @io_out.pop
|
66
|
+
o_io.close unless o_io.closed?
|
67
|
+
run @ios.delete(o_io), &@close_callback
|
68
|
+
end
|
69
|
+
# react to IO events
|
70
|
+
begin
|
71
|
+
r = IO.select(@ios.keys, nil, nil, 0.15)
|
72
|
+
r[0].each {|io| @queue << [@ios[io]] } if r
|
73
|
+
rescue => e
|
74
|
+
|
75
|
+
end
|
76
|
+
unless @stop && @queue.empty?
|
77
|
+
# @ios.values.each &@timeout_loop
|
78
|
+
@check_timers && (@queue << @check_timers)
|
79
|
+
@queue << @reactor
|
80
|
+
end
|
81
|
+
else
|
82
|
+
@queue << @reactor
|
83
|
+
end
|
84
|
+
end )]
|
85
|
+
end
|
86
|
+
|
87
|
+
def initialize_timers
|
88
|
+
@timer_locker = Mutex.new
|
89
|
+
@timers = []
|
90
|
+
# cycles through timed jobs, executing and/or deleting them if their time has come.
|
91
|
+
@check_timers = [(Proc.new do
|
92
|
+
@timer_locker.synchronize { @timers.delete_if {|t| t.done? } }
|
93
|
+
end)]
|
94
|
+
end
|
95
|
+
|
96
|
+
def initialize_tasks
|
97
|
+
# initializes actions to be taken when starting to run
|
98
|
+
run do
|
99
|
+
@protocol ||= :cycle if @timers.any?
|
100
|
+
next unless @protocol
|
101
|
+
if @protocol.is_a?( ::Class ) && @protocol.ancestors.include?( ::Iodine::Protocol )
|
102
|
+
begin
|
103
|
+
@server = ::TCPServer.new(@bind, @port)
|
104
|
+
rescue => e
|
105
|
+
fatal e.message
|
106
|
+
fatal "Running existing tasks and exiting."
|
107
|
+
@queue << @reactor
|
108
|
+
Process.kill("INT", 0)
|
109
|
+
next
|
110
|
+
end
|
111
|
+
on_shutdown do
|
112
|
+
log "Stopping to listen on port #{@port} and shutting down.\n"
|
113
|
+
@server.close unless @server.closed?
|
114
|
+
end
|
115
|
+
::Iodine::Base::Listener.accept(@server, false)
|
116
|
+
log "Iodine #{VERSION} is listening on port #{@port}#{ ' to SSL/TLS connections.' if @ssl}\n"
|
117
|
+
if @spawn_count && @spawn_count.to_i > 1 && Process.respond_to?(:fork)
|
118
|
+
log "Server will run using #{@spawn_count.to_i} processes - Spawning #{@spawn_count.to_i - 1 } more processes.\n"
|
119
|
+
(@spawn_count.to_i - 1).times do
|
120
|
+
Process.fork do
|
121
|
+
log "Spawned process: #{Process.pid}.\n"
|
122
|
+
on_shutdown { log "Shutting down process #{Process.pid}.\n" }
|
123
|
+
@queue.clear
|
124
|
+
@queue << @reactor
|
125
|
+
startup false, true
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
130
|
+
log "Press ^C to stop the server.\n"
|
131
|
+
else
|
132
|
+
log "#{self == Iodine ? 'Iodine' : "#{self.name} (Iodine)"} #{VERSION} is running.\n"
|
133
|
+
log "Press ^C to stop the cycling.\n"
|
134
|
+
end
|
135
|
+
on_shutdown do
|
136
|
+
shut_down_proc = Proc.new {|prot| prot.on_shutdown ; prot.close }
|
137
|
+
@ios.values.each {|p| run p, &shut_down_proc }
|
138
|
+
@queue << @reactor
|
139
|
+
end
|
140
|
+
@queue << @reactor
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
@@ -6,10 +6,7 @@ module Iodine
|
|
6
6
|
module_function
|
7
7
|
def run(app, options = {})
|
8
8
|
@app = app
|
9
|
-
|
10
|
-
puts "reading threads = #{Iodine.threads.to_s}"
|
11
|
-
|
12
|
-
Iodine.threads = 18 unless Iodine.threads
|
9
|
+
Iodine.threads ||= 18
|
13
10
|
Iodine.port = options[:Port]
|
14
11
|
Iodine.protocol ||= Iodine::Http::Http1
|
15
12
|
@pre_rack_handler = Iodine::Http.on_http unless Iodine::Http.on_http == Iodine::Http::NOT_IMPLEMENTED
|
@@ -10,15 +10,19 @@ module Iodine
|
|
10
10
|
|
11
11
|
attr_accessor :response, :request, :params
|
12
12
|
|
13
|
-
def initialize
|
13
|
+
def initialize options
|
14
14
|
@response = nil
|
15
|
-
@
|
16
|
-
@
|
17
|
-
@on_message = @params[:on_message]
|
15
|
+
@options = options
|
16
|
+
@on_message = @options[:on_message]
|
18
17
|
raise "Websocket client must have an #on_message Proc or handler." unless @on_message && @on_message.respond_to?(:call)
|
19
|
-
@on_open = @
|
20
|
-
@on_close = @
|
21
|
-
@
|
18
|
+
@on_open = @options[:on_open]
|
19
|
+
@on_close = @options[:on_close]
|
20
|
+
@on_error = @options[:on_error]
|
21
|
+
@renew = @options[:renew].to_i
|
22
|
+
@options[:url] = URI.parse(@options[:url]) unless @options[:url].is_a?(URI)
|
23
|
+
@connection_lock = Mutex.new
|
24
|
+
raise TypeError, "Websocket Client `:send` should be either a String or a Proc object." if @options[:send] && !(@options[:send].is_a?(String) || @options[:send].is_a?(Proc))
|
25
|
+
on_close && (@io || raise("Connection error, cannot create websocket client")) unless connect
|
22
26
|
end
|
23
27
|
|
24
28
|
def on event_name, &block
|
@@ -39,17 +43,25 @@ module Iodine
|
|
39
43
|
@on_message = block if block
|
40
44
|
return @on_message
|
41
45
|
end
|
42
|
-
|
46
|
+
begin
|
47
|
+
instance_exec( data, &@on_message)
|
48
|
+
rescue => e
|
49
|
+
@on_error ? @on_error.call(e) : raise(e)
|
50
|
+
end
|
43
51
|
end
|
44
52
|
|
45
53
|
def on_open
|
46
54
|
raise 'The on_open even is invalid at this point.' if block_given?
|
55
|
+
@renew = @options[:renew].to_i
|
47
56
|
@io = @request[:io]
|
48
57
|
Iodine::Http::Request.parse @request
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
58
|
+
begin
|
59
|
+
instance_exec(&@on_open) if @on_open
|
60
|
+
rescue => e
|
61
|
+
@on_error ? @on_error.call(e) : raise(e)
|
62
|
+
end
|
63
|
+
if @options[:every] && @options[:send]
|
64
|
+
Iodine.run_every @options[:every], self, @options do |ws, client_params, timer|
|
53
65
|
if ws.closed?
|
54
66
|
timer.stop!
|
55
67
|
next
|
@@ -57,7 +69,11 @@ module Iodine
|
|
57
69
|
if client_params[:send].is_a?(String)
|
58
70
|
ws.write client_params[:send]
|
59
71
|
elsif client_params[:send].is_a?(Proc)
|
60
|
-
|
72
|
+
begin
|
73
|
+
ws.instance_exec(&client_params[:send])
|
74
|
+
rescue => e
|
75
|
+
@on_error ? @on_error.call(e) : raise(e)
|
76
|
+
end
|
61
77
|
end
|
62
78
|
end
|
63
79
|
end
|
@@ -67,25 +83,35 @@ module Iodine
|
|
67
83
|
return @on_close = block if block
|
68
84
|
if @renew > 0
|
69
85
|
renew_proc = Proc.new do
|
86
|
+
@io = nil
|
70
87
|
begin
|
71
|
-
|
88
|
+
raise unless connect
|
72
89
|
rescue
|
73
90
|
@renew -= 1
|
74
91
|
if @renew <= 0
|
75
|
-
Iodine.fatal "WebsocketClient renewal FAILED for #{@
|
76
|
-
|
92
|
+
Iodine.fatal "WebsocketClient renewal FAILED for #{@options[:url]}"
|
93
|
+
on_close
|
77
94
|
else
|
78
|
-
Iodine.
|
79
|
-
|
95
|
+
Iodine.warn "WebsocketClient renewal failed for #{@options[:url]}, #{@renew} attempts left"
|
96
|
+
renew_proc.call
|
80
97
|
end
|
81
98
|
false
|
82
99
|
end
|
83
100
|
end
|
84
|
-
renew_proc.call
|
101
|
+
@connection_lock.synchronize { renew_proc.call }
|
85
102
|
else
|
86
|
-
|
103
|
+
begin
|
104
|
+
instance_exec(&@on_close) if @on_close
|
105
|
+
rescue => e
|
106
|
+
@on_error ? @on_error.call(e) : raise(e)
|
107
|
+
end
|
87
108
|
end
|
88
109
|
end
|
110
|
+
def on_error(error = nil, &block)
|
111
|
+
return @on_error = block if block
|
112
|
+
instance_exec(error, &@on_error) if @on_error
|
113
|
+
on_close unless @io # if the connection was initialized, :on_close will be called by Iodine
|
114
|
+
end
|
89
115
|
|
90
116
|
def on_shutdown
|
91
117
|
@renew = 0
|
@@ -120,7 +146,6 @@ module Iodine
|
|
120
146
|
# @return [true, false] Returns the true if the data was actually sent or nil if no data was sent.
|
121
147
|
def write data, op_code = nil, fin = true, ext = 0
|
122
148
|
return false if !data || data.empty?
|
123
|
-
return false if @io.closed?
|
124
149
|
data = data.dup # needed?
|
125
150
|
unless op_code # apply extenetions to the message as a whole
|
126
151
|
op_code = (data.encoding == ::Encoding::UTF_8 ? 1 : 2)
|
@@ -129,8 +154,8 @@ module Iodine
|
|
129
154
|
byte_size = data.bytesize
|
130
155
|
if byte_size > (::Iodine::Http::Websockets::FRAME_SIZE_LIMIT+2)
|
131
156
|
sections = byte_size/FRAME_SIZE_LIMIT + (byte_size % ::Iodine::Http::Websockets::FRAME_SIZE_LIMIT ? 1 : 0)
|
132
|
-
|
133
|
-
return
|
157
|
+
ret = write( data.slice!( 0...::Iodine::Http::Websockets::FRAME_SIZE_LIMIT ), op_code, data.empty?, ext) && (ext = op_code = 0) until data.empty?
|
158
|
+
return ret # avoid sending an empty frame.
|
134
159
|
end
|
135
160
|
# @ws_extentions.each { |ex| ext |= ex.edit_frame data } if @ws_extentions
|
136
161
|
header = ( (fin ? 0b10000000 : 0) | (op_code & 0b00001111) | ext).chr.force_encoding(::Encoding::ASCII_8BIT)
|
@@ -147,74 +172,22 @@ module Iodine
|
|
147
172
|
@@make_mask_proc ||= Proc.new {Random.rand(251) + 1}
|
148
173
|
mask = Array.new(4, &(@@make_mask_proc))
|
149
174
|
header << mask.pack('C*'.freeze)
|
150
|
-
@
|
151
|
-
|
152
|
-
|
175
|
+
@connection_lock.synchronize do
|
176
|
+
return false if @io.nil? || @io.closed?
|
177
|
+
@io.write header
|
178
|
+
i = -1;
|
179
|
+
@io.write(data.bytes.map! {|b| (b ^ mask[i = (i + 1)%4]) } .pack('C*'.freeze)) && true
|
180
|
+
end
|
153
181
|
end
|
154
182
|
alias :<< :write
|
155
183
|
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
# options:: a Hash with options to be used. The options will be used to define the connection's details (i.e. ssl etc') and the Websocket callbacks (i.e. on_open(ws), on_close(ws), on_message(ws))
|
161
|
-
# &block:: an optional block that accepts one parameter (data) and will be used as the `#on_message(data)`
|
162
|
-
#
|
163
|
-
# Acceptable options are:
|
164
|
-
# on_open:: the on_open callback. Must be an objects that answers `call(ws)`, usually a Proc.
|
165
|
-
# on_message:: the on_message callback. Must be an objects that answers `call(ws)`, usually a Proc.
|
166
|
-
# on_close:: the on_close callback - this will ONLY be called if the connection WASN'T renewed. Must be an objects that answers `call(ws)`, usually a Proc.
|
167
|
-
# headers:: a Hash of custom HTTP headers to be sent with the request. Header data, including cookie headers, should be correctly encoded.
|
168
|
-
# cookies:: a Hash of cookies to be sent with the request. cookie data will be encoded before being sent.
|
169
|
-
# timeout:: the number of seconds to wait before the connection is established. Defaults to 5 seconds.
|
170
|
-
# every:: this option, together with `:send` and `:renew`, implements a polling websocket. :every is the number of seconds between each polling event. without `:send`, this option will be ignored. defaults to nil.
|
171
|
-
# send:: a String to be sent or a Proc to be performed each polling interval. This option, together with `:every` and `:renew`, implements a polling websocket. without `:every`, this option will be ignored. defaults to nil. If `:send` is a Proc, it will be executed within the context of the websocket client object, with acess to the websocket client's instance variables and methods.
|
172
|
-
# renew:: the number of times to attempt to renew the connection if the connection is terminated by the remote server. Attempts are made in 2 seconds interval. The default for a polling websocket is 5 attempts to renew. For all other clients, the default is 0 (no renewal).
|
173
|
-
#
|
174
|
-
# The method will block until the connection is established or until 5 seconds have passed (the timeout). The method will either return a WebsocketClient instance object or raise an exception it the connection was unsuccessful.
|
175
|
-
#
|
176
|
-
# Use Iodine::Http.ws_connect for a non-blocking initialization.
|
177
|
-
#
|
178
|
-
# An #on_close callback will only be called if the connection isn't or cannot be renewed. If the connection is renewed,
|
179
|
-
# the #on_open callback will be called again for a new Websocket client instance - but the #on_close callback will NOT be called.
|
180
|
-
#
|
181
|
-
# Due to this design, the #on_open and #on_close methods should NOT be used for opening IO resources (i.e. file handles) nor for cleanup IF the `:renew` option is enabled.
|
182
|
-
#
|
183
|
-
# An on_message Proc must be defined, or the method will fail.
|
184
|
-
#
|
185
|
-
# The on_message Proc can be defined using the optional block:
|
186
|
-
#
|
187
|
-
# Iodine::Http::WebsocketClient.connect("ws://localhost:3000/") {|data| write data} #echo example
|
188
|
-
#
|
189
|
-
# OR, the on_message Proc can be defined using the options Hash:
|
190
|
-
#
|
191
|
-
# Iodine::Http::WebsocketClient.connect("ws://localhost:3000/", on_open: -> {}, on_message: -> {|data| write data })
|
192
|
-
#
|
193
|
-
# The #on_message(data), #on_open and #on_close methods will be executed within the context of the WebsocketClient
|
194
|
-
# object, and will have native acess to the Websocket response object.
|
195
|
-
#
|
196
|
-
# After the WebsocketClient had been created, it's possible to update the #on_message and #on_close methods:
|
197
|
-
#
|
198
|
-
# # updates #on_message
|
199
|
-
# wsclient.on_message do |data|
|
200
|
-
# response << "I'll disconnect on the next message!"
|
201
|
-
# # updates #on_message again.
|
202
|
-
# on_message {|data| disconnect }
|
203
|
-
# end
|
204
|
-
#
|
205
|
-
#
|
206
|
-
# !!please be aware that the Websockt Client will not attempt to verify SSL certificates,
|
207
|
-
# so that even SSL connections are vulnerable to a possible man in the middle attack.
|
208
|
-
#
|
209
|
-
# @return [Iodine::Http::WebsocketClient] this method returns the connected {Iodine::Http::WebsocketClient} or raises an exception if something went wrong (such as a connection timeout).
|
210
|
-
def self.connect url, options={}, &block
|
184
|
+
protected
|
185
|
+
|
186
|
+
def connect
|
187
|
+
return false if @io && !@io.closed?
|
211
188
|
socket = nil
|
212
|
-
|
213
|
-
options[:
|
214
|
-
raise "No #on_message handler defined! please pass a block or define an #on_message handler!" unless options[:on_message]
|
215
|
-
url = URI.parse(url) unless url.is_a?(URI)
|
216
|
-
options[:url] = url
|
217
|
-
options[:renew] ||= 5 if options[:every] && options[:send]
|
189
|
+
url = @options[:url]
|
190
|
+
@options[:renew] ||= 5 if @options[:every] && @options[:send]
|
218
191
|
|
219
192
|
ssl = url.scheme == "https" || url.scheme == "wss"
|
220
193
|
|
@@ -225,33 +198,32 @@ module Iodine
|
|
225
198
|
context = OpenSSL::SSL::SSLContext.new
|
226
199
|
context.cert_store = OpenSSL::X509::Store.new
|
227
200
|
context.cert_store.set_default_paths
|
228
|
-
context.set_params verify_mode: (options[:verify_mode] || OpenSSL::SSL::VERIFY_NONE) # OpenSSL::SSL::VERIFY_PEER #OpenSSL::SSL::VERIFY_NONE
|
201
|
+
context.set_params verify_mode: (@options[:verify_mode] || OpenSSL::SSL::VERIFY_NONE) # OpenSSL::SSL::VERIFY_PEER #OpenSSL::SSL::VERIFY_NONE
|
229
202
|
ssl = OpenSSL::SSL::SSLSocket.new(socket, context)
|
230
203
|
ssl.sync_close = true
|
231
204
|
ssl.connect
|
232
205
|
end
|
233
206
|
# prep custom headers
|
234
207
|
custom_headers = ''
|
235
|
-
custom_headers = options[:headers] if options[:headers].is_a?(String)
|
236
|
-
options[:headers].each {|k, v| custom_headers << "#{k.to_s}: #{v.to_s}\r\n"} if options[:headers].is_a?(Hash)
|
237
|
-
options[:cookies].each {|k, v| raise 'Illegal cookie name' if k.to_s.match(/[\x00-\x20\(\)<>@,;:\\\"\/\[\]\?\=\{\}\s]/); custom_headers << "Cookie: #{ k }=#{ Iodine::Http::Request.encode_url v }\r\n"} if options[:cookies].is_a?(Hash)
|
208
|
+
custom_headers = @options[:headers] if @options[:headers].is_a?(String)
|
209
|
+
@options[:headers].each {|k, v| custom_headers << "#{k.to_s}: #{v.to_s}\r\n"} if @options[:headers].is_a?(Hash)
|
210
|
+
@options[:cookies].each {|k, v| raise 'Illegal cookie name' if k.to_s.match(/[\x00-\x20\(\)<>@,;:\\\"\/\[\]\?\=\{\}\s]/); custom_headers << "Cookie: #{ k }=#{ Iodine::Http::Request.encode_url v }\r\n"} if @options[:cookies].is_a?(Hash)
|
238
211
|
|
239
212
|
# send protocol upgrade request
|
240
213
|
websocket_key = [(Array.new(16) {rand 255} .pack 'c*' )].pack('m0*')
|
241
|
-
(ssl || socket).write "GET #{url.path}#{url.query.to_s.empty? ? '' : ('?' + url.query)} HTTP/1.1\r\nHost: #{url.host}#{url.port ? (':'+url.port.to_s) : ''}\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nOrigin: #{
|
214
|
+
(ssl || socket).write "GET #{url.path}#{url.query.to_s.empty? ? '' : ('?' + url.query)} HTTP/1.1\r\nHost: #{url.host}#{url.port ? (':'+url.port.to_s) : ''}\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nOrigin: #{ssl ? 'https' : 'http'}://#{url.host}\r\nSec-WebSocket-Key: #{websocket_key}\r\nSec-WebSocket-Version: 13\r\n#{custom_headers}\r\n"
|
242
215
|
# wait for answer - make sure we don't over-read
|
243
216
|
# (a websocket message might be sent immidiately after connection is established)
|
244
217
|
reply = ''
|
245
218
|
reply.force_encoding(::Encoding::ASCII_8BIT)
|
246
|
-
stop_time = Time.now + (options[:timeout] || 5)
|
219
|
+
stop_time = Time.now + (@options[:timeout] || 5)
|
247
220
|
stop_reply = "\r\n\r\n"
|
248
|
-
sleep 0.2
|
249
221
|
until reply[-4..-1] == stop_reply
|
250
222
|
begin
|
251
223
|
reply << ( ssl ? ssl.read_nonblock(1) : socket.recv_nonblock(1) )
|
252
|
-
rescue Errno::EWOULDBLOCK => e
|
224
|
+
rescue Errno::EWOULDBLOCK, OpenSSL::SSL::SSLErrorWaitReadable => e
|
253
225
|
raise "Websocket client handshake timed out (HTTP reply not recieved)\n\n Got Only: #{reply}" if Time.now >= stop_time
|
254
|
-
IO.select [socket], nil, nil, (options[:timeout] || 5)
|
226
|
+
IO.select [socket], nil, nil, (@options[:timeout] || 5)
|
255
227
|
retry
|
256
228
|
end
|
257
229
|
raise "Connection failed" if socket.closed?
|
@@ -260,22 +232,22 @@ module Iodine
|
|
260
232
|
raise "Connection Refused. Reply was:\r\n #{reply}" unless reply.lines[0].match(/^HTTP\/[\d\.]+ 101/i)
|
261
233
|
raise 'Websocket Key Authentication failed.' unless reply.match(/^Sec-WebSocket-Accept:[\s]*([^\s]*)/i) && reply.match(/^Sec-WebSocket-Accept:[\s]*([^\s]*)/i)[1] == Digest::SHA1.base64digest(websocket_key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')
|
262
234
|
# read the body's data and parse any incoming data.
|
263
|
-
request = Iodine::Http::Request.new
|
264
|
-
request[:method] = 'GET'
|
265
|
-
request['host'] = "#{url.host}:#{url.port}"
|
266
|
-
request[:query] = url.path
|
267
|
-
request[:version] = '1.1'
|
235
|
+
@request = Iodine::Http::Request.new
|
236
|
+
@request[:method] = 'GET'
|
237
|
+
@request['host'] = "#{url.host}:#{url.port}"
|
238
|
+
@request[:query] = url.path
|
239
|
+
@request[:version] = '1.1'
|
268
240
|
reply = StringIO.new reply
|
269
241
|
reply.gets
|
270
242
|
|
271
243
|
until reply.eof?
|
272
|
-
until request[:headers_complete] || (l = reply.gets).nil?
|
244
|
+
until @request[:headers_complete] || (l = reply.gets).nil?
|
273
245
|
if l.include? ':'
|
274
246
|
l = l.strip.split(/:[\s]?/, 2)
|
275
247
|
l[0].strip! ; l[0].downcase!;
|
276
|
-
request[l[0]] ? (request[l[0]].is_a?(Array) ? (request[l[0]] << l[1]) : request[l[0]] = [request[l[0]], l[1] ]) : (request[l[0]] = l[1])
|
248
|
+
@request[l[0]] ? (@request[l[0]].is_a?(Array) ? (@request[l[0]] << l[1]) : @request[l[0]] = [@request[l[0]], l[1] ]) : (@request[l[0]] = l[1])
|
277
249
|
elsif l =~ /^[\r]?\n/
|
278
|
-
request[:headers_complete] = true
|
250
|
+
@request[:headers_complete] = true
|
279
251
|
else
|
280
252
|
#protocol error
|
281
253
|
raise 'Protocol Error, closing connection.'
|
@@ -285,15 +257,80 @@ module Iodine
|
|
285
257
|
end
|
286
258
|
reply.string.clear
|
287
259
|
|
288
|
-
request
|
289
|
-
client = self.new(request)
|
290
|
-
Iodine::Http::Websockets.new( ( ssl || socket), handler: client, request: request )
|
260
|
+
return Iodine::Http::Websockets.new( ( ssl || socket), handler: self, request: @request )
|
291
261
|
|
292
|
-
|
262
|
+
rescue => e
|
263
|
+
(ssl || socket).tap {|io| next if io.nil?; io.close unless io.closed?}
|
264
|
+
if @options[:on_error]
|
265
|
+
@options[:on_error].call(e)
|
266
|
+
return false
|
267
|
+
end
|
268
|
+
raise e unless @io
|
269
|
+
end
|
293
270
|
|
294
|
-
|
295
|
-
|
296
|
-
|
271
|
+
# Create a simple Websocket Client(!).
|
272
|
+
#
|
273
|
+
# This method accepts two parameters:
|
274
|
+
# url:: a String representing the URL of the websocket. i.e.: 'ws://foo.bar.com:80/ws/path'
|
275
|
+
# options:: a Hash with options to be used. The options will be used to define the connection's details (i.e. ssl etc') and the Websocket callbacks (i.e. on_open(ws), on_close(ws), on_message(ws))
|
276
|
+
# &block:: an optional block that accepts one parameter (data) and will be used as the `#on_message(data)`
|
277
|
+
#
|
278
|
+
# Acceptable options are:
|
279
|
+
# on_open:: the on_open callback - Must be an objects that answers `call()`, usually a Proc.
|
280
|
+
# on_message:: the on_message callback - Must be an objects that answers `call(data)`, usually a Proc.
|
281
|
+
# on_close:: the on_close callback - Must be an objects that answers `call()`, usually a Proc. The method is called when the connection is closed and isn't renewed.
|
282
|
+
# on_error:: the on_error callback - Must be an objects that answers `call(err)`, usually a Proc. This is called whenever a connection fails to be established or an exception is raised by any of the callbacks. This is NOT the disconnection websocket message. dafaults to raising the error (error pass-through).
|
283
|
+
# headers:: a Hash of custom HTTP headers to be sent with the request. Header data, including cookie headers, should be correctly encoded.
|
284
|
+
# cookies:: a Hash of cookies to be sent with the request. cookie data will be encoded before being sent.
|
285
|
+
# timeout:: the number of seconds to wait before the connection is established. Defaults to 5 seconds.
|
286
|
+
# every:: this option, together with `:send` and `:renew`, implements a polling websocket. :every is the number of seconds between each polling event. without `:send`, this option will be ignored. defaults to nil.
|
287
|
+
# send:: a String to be sent or a Proc to be performed each polling interval. This option, together with `:every` and `:renew`, implements a polling websocket. without `:every`, this option will be ignored. defaults to nil. If `:send` is a Proc, it will be executed within the context of the websocket client object, with acess to the websocket client's instance variables and methods.
|
288
|
+
# renew:: the number of times to attempt to renew the connection if the connection is terminated by the remote server. Attempts are made in 2 seconds interval. The default for a polling websocket is 5 attempts to renew. For all other clients, the default is 0 (no renewal).
|
289
|
+
#
|
290
|
+
# The method will block until the connection is established or until 5 seconds have passed (the timeout). The method will either return a WebsocketClient instance object or raise an exception it the connection was unsuccessful.
|
291
|
+
#
|
292
|
+
# Use Iodine::Http.ws_connect for a non-blocking initialization.
|
293
|
+
#
|
294
|
+
# An #on_close callback will only be called if the connection isn't or cannot be renewed. If the connection is renewed,
|
295
|
+
# the #on_open callback will be called again for a new Websocket client instance - but the #on_close callback will NOT be called.
|
296
|
+
#
|
297
|
+
# Due to this design, the #on_open and #on_close methods should NOT be used for opening IO resources (i.e. file handles) nor for cleanup IF the `:renew` option is enabled.
|
298
|
+
#
|
299
|
+
# An on_message Proc must be defined, or the method will fail.
|
300
|
+
#
|
301
|
+
# The on_message Proc can be defined using the optional block:
|
302
|
+
#
|
303
|
+
# Iodine::Http::WebsocketClient.connect("ws://localhost:3000/") {|data| write data} #echo example
|
304
|
+
#
|
305
|
+
# OR, the on_message Proc can be defined using the options Hash:
|
306
|
+
#
|
307
|
+
# Iodine::Http::WebsocketClient.connect("ws://localhost:3000/", on_open: -> {}, on_message: -> {|data| write data })
|
308
|
+
#
|
309
|
+
# The #on_message(data), #on_open and #on_close methods will be executed within the context of the WebsocketClient
|
310
|
+
# object, and will have native acess to the Websocket response object.
|
311
|
+
#
|
312
|
+
# After the WebsocketClient had been created, it's possible to update the #on_message and #on_close methods:
|
313
|
+
#
|
314
|
+
# # updates #on_message
|
315
|
+
# wsclient.on_message do |data|
|
316
|
+
# response << "I'll disconnect on the next message!"
|
317
|
+
# # updates #on_message again.
|
318
|
+
# on_message {|data| disconnect }
|
319
|
+
# end
|
320
|
+
#
|
321
|
+
#
|
322
|
+
# !!please be aware that the Websockt Client will not attempt to verify SSL certificates,
|
323
|
+
# so that even SSL connections are vulnerable to a possible man in the middle attack.
|
324
|
+
#
|
325
|
+
# @return [Iodine::Http::WebsocketClient] this method returns the connected {Iodine::Http::WebsocketClient} or raises an exception if something went wrong (such as a connection timeout).
|
326
|
+
def self.connect url, options={}, &block
|
327
|
+
options = url if url.is_a?(Hash) && options.empty?
|
328
|
+
options[:renew] ||= 5 if options[:every] && options[:send]
|
329
|
+
options[:url] ||= url
|
330
|
+
options[:on_message] ||= block
|
331
|
+
client = self.new(options)
|
332
|
+
return client unless client.closed?
|
333
|
+
false
|
297
334
|
end
|
298
335
|
end
|
299
336
|
end
|
data/lib/iodine/io.rb
CHANGED
@@ -26,48 +26,6 @@ module Iodine
|
|
26
26
|
|
27
27
|
protected
|
28
28
|
|
29
|
-
@port = ((ARGV.index('-p') && ARGV[ARGV.index('-p') + 1]) || ENV['PORT'] || 3000).to_i
|
30
|
-
@bind = (ARGV.index('-ip') && ARGV[ARGV.index('-ip') + 1]) || ENV['IP'] || "0.0.0.0"
|
31
|
-
@ssl = (ARGV.index('ssl') && true) || (@port == 443)
|
32
|
-
@protocol = nil
|
33
|
-
@ssl_context = nil
|
34
|
-
@ssl_protocols = {}
|
35
|
-
@time = Time.now
|
36
|
-
|
37
|
-
@timeout_proc = Proc.new {|prot| prot.timeout?(@time) }
|
38
|
-
@status_loop = Proc.new {|io| @io_out << io if io.closed? || !(io.stat.readable? rescue false) }
|
39
|
-
@close_callback = Proc.new {|prot| prot.on_close if prot }
|
40
|
-
REACTOR = [ (Proc.new do
|
41
|
-
if @queue.empty?
|
42
|
-
#clear any closed IO objects.
|
43
|
-
@time = Time.now
|
44
|
-
@ios.keys.each &@status_loop
|
45
|
-
@ios.values.each &@timeout_proc
|
46
|
-
until @io_in.empty?
|
47
|
-
n_io = @io_in.pop
|
48
|
-
@ios[n_io[0]] = n_io[1]
|
49
|
-
end
|
50
|
-
until @io_out.empty?
|
51
|
-
o_io = @io_out.pop
|
52
|
-
o_io.close unless o_io.closed?
|
53
|
-
run @ios.delete(o_io), &@close_callback
|
54
|
-
end
|
55
|
-
# react to IO events
|
56
|
-
begin
|
57
|
-
r = IO.select(@ios.keys, nil, nil, 0.15)
|
58
|
-
r[0].each {|io| @queue << [@ios[io]] } if r
|
59
|
-
rescue => e
|
60
|
-
|
61
|
-
end
|
62
|
-
unless @stop && @queue.empty?
|
63
|
-
# @ios.values.each &@timeout_loop
|
64
|
-
@check_timers && (@queue << @check_timers)
|
65
|
-
@queue << REACTOR
|
66
|
-
end
|
67
|
-
else
|
68
|
-
@queue << REACTOR
|
69
|
-
end
|
70
|
-
end )]
|
71
29
|
# internal helper methods and classes.
|
72
30
|
module Base
|
73
31
|
# the server listener Protocol.
|
@@ -95,50 +53,4 @@ module Iodine
|
|
95
53
|
end
|
96
54
|
end
|
97
55
|
end
|
98
|
-
|
99
|
-
########
|
100
|
-
## remember to set traps (once) when 'listen' is called.
|
101
|
-
run do
|
102
|
-
next unless @protocol
|
103
|
-
if @protocol.is_a?( ::Class ) && @protocol.ancestors.include?( ::Iodine::Protocol )
|
104
|
-
begin
|
105
|
-
@server = ::TCPServer.new(@bind, @port)
|
106
|
-
rescue => e
|
107
|
-
fatal e.message
|
108
|
-
fatal "Running existing tasks and exiting."
|
109
|
-
@queue << REACTOR
|
110
|
-
Process.kill("INT", 0)
|
111
|
-
next
|
112
|
-
end
|
113
|
-
on_shutdown do
|
114
|
-
log "Stopping to listen on port #{@port} and shutting down.\n"
|
115
|
-
@server.close unless @server.closed?
|
116
|
-
end
|
117
|
-
::Iodine::Base::Listener.accept(@server, false)
|
118
|
-
log "Iodine #{VERSION} is listening on port #{@port}#{ ' to SSL/TLS connections.' if @ssl}\n"
|
119
|
-
if @spawn_count && @spawn_count.to_i > 1 && Process.respond_to?(:fork)
|
120
|
-
log "Server will run using #{@spawn_count.to_i} processes - Spawning #{@spawn_count.to_i - 1 } more processes.\n"
|
121
|
-
(@spawn_count.to_i - 1).times do
|
122
|
-
Process.fork do
|
123
|
-
log "Spawned process: #{Process.pid}.\n"
|
124
|
-
on_shutdown { log "Shutting down process #{Process.pid}.\n" }
|
125
|
-
@queue.clear
|
126
|
-
@queue << REACTOR
|
127
|
-
startup false, true
|
128
|
-
end
|
129
|
-
end
|
130
|
-
|
131
|
-
end
|
132
|
-
log "Press ^C to stop the server.\n"
|
133
|
-
else
|
134
|
-
log "Iodine #{VERSION} is running.\n"
|
135
|
-
log "Press ^C to stop the cycling.\n"
|
136
|
-
end
|
137
|
-
on_shutdown do
|
138
|
-
shut_down_proc = Proc.new {|prot| prot.on_shutdown ; prot.close }
|
139
|
-
@ios.values.each {|p| run p, &shut_down_proc }
|
140
|
-
@queue << REACTOR
|
141
|
-
end
|
142
|
-
@queue << REACTOR
|
143
|
-
end
|
144
56
|
end
|
data/lib/iodine/ssl_connector.rb
CHANGED
@@ -3,8 +3,9 @@ module Iodine
|
|
3
3
|
# This is a mini-protocol used only to implement the SSL Handshake in a non-blocking manner,
|
4
4
|
# allowing for a hardcoded timeout (which you can monkey patch) of 3 seconds.
|
5
5
|
class SSLConnector < Protocol
|
6
|
-
def initialize io, protocol
|
6
|
+
def initialize io, protocol, options = nil
|
7
7
|
@protocol = protocol
|
8
|
+
@options = options
|
8
9
|
super(io)
|
9
10
|
end
|
10
11
|
TIMEOUT = 3 # hardcoded SSL/TLS handshake timeout
|
@@ -34,7 +35,7 @@ module Iodine
|
|
34
35
|
ensure
|
35
36
|
@locker.unlock
|
36
37
|
end
|
37
|
-
( (@ssl_socket.npn_protocol && ::Iodine.ssl_protocols[@ssl_socket.npn_protocol]) || @protocol).new @ssl_socket
|
38
|
+
( (@ssl_socket.npn_protocol && ::Iodine.ssl_protocols[@ssl_socket.npn_protocol]) || @protocol).new @ssl_socket, @options
|
38
39
|
end
|
39
40
|
def on_close
|
40
41
|
# inform
|
data/lib/iodine/timers.rb
CHANGED
@@ -17,10 +17,11 @@ module Iodine
|
|
17
17
|
|
18
18
|
# Initialize a timed event.
|
19
19
|
def initialize reactor, interval, repeat_limit = -1, args=[], job=nil
|
20
|
+
@reactor = reactor
|
20
21
|
@interval = interval
|
21
22
|
@repeat_limit = repeat_limit ? repeat_limit.to_i : -1
|
22
23
|
@job = job || (Proc.new { stop! })
|
23
|
-
@next =
|
24
|
+
@next = @reactor.time + interval
|
24
25
|
args << self
|
25
26
|
@args = args
|
26
27
|
end
|
@@ -37,11 +38,11 @@ module Iodine
|
|
37
38
|
# If the timed event is due, this method will also add the event to the queue.
|
38
39
|
# @return [true, false]
|
39
40
|
def done?
|
40
|
-
return false unless @next <=
|
41
|
+
return false unless @next <= @reactor.time
|
41
42
|
return true if @repeat_limit == 0
|
42
43
|
@repeat_limit -= 1 if @repeat_limit.to_i > 0
|
43
|
-
|
44
|
-
@next =
|
44
|
+
@reactor.run *@args, &@job
|
45
|
+
@next = @reactor.time + @interval
|
45
46
|
@repeat_limit == 0
|
46
47
|
end
|
47
48
|
end
|
@@ -85,17 +86,9 @@ module Iodine
|
|
85
86
|
end
|
86
87
|
|
87
88
|
protected
|
88
|
-
@timer_locker = Mutex.new
|
89
|
-
@timers = []
|
90
89
|
|
91
90
|
# Creates a TimedEvent object and adds it to the Timers stack.
|
92
91
|
def timed_job seconds, limit = false, args = [], block = nil
|
93
92
|
@timer_locker.synchronize {@timers << TimedEvent.new(self, seconds, limit, args, block); @timers.last}
|
94
93
|
end
|
95
|
-
# cycles through timed jobs, executing and/or deleting them if their time has come.
|
96
|
-
@check_timers = Proc.new do
|
97
|
-
@timer_locker.synchronize { @timers.delete_if {|t| t.done? } }
|
98
|
-
end
|
99
|
-
@check_timers = [@check_timers]
|
100
|
-
|
101
94
|
end
|
data/lib/iodine/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: iodine
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.9
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Boaz Segev
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-11-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -76,6 +76,7 @@ files:
|
|
76
76
|
- lib/iodine.rb
|
77
77
|
- lib/iodine/client.rb
|
78
78
|
- lib/iodine/core.rb
|
79
|
+
- lib/iodine/core_init.rb
|
79
80
|
- lib/iodine/http.rb
|
80
81
|
- lib/iodine/http/hpack.rb
|
81
82
|
- lib/iodine/http/http1.rb
|