iodine 0.5.2 → 0.6.0
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 +14 -0
- data/README.md +63 -100
- data/bin/raw-rbhttp +12 -7
- data/examples/config.ru +8 -7
- data/examples/echo.ru +8 -7
- data/examples/info.md +41 -35
- data/examples/pubsub_engine.ru +12 -12
- data/examples/redis.ru +10 -12
- data/examples/shootout.ru +19 -42
- data/exe/iodine +116 -1
- data/ext/iodine/defer.c +1 -1
- data/ext/iodine/facil.c +12 -8
- data/ext/iodine/facil.h +2 -2
- data/ext/iodine/iodine.c +177 -343
- data/ext/iodine/iodine.h +18 -72
- data/ext/iodine/iodine_caller.c +132 -0
- data/ext/iodine/iodine_caller.h +21 -0
- data/ext/iodine/iodine_connection.c +841 -0
- data/ext/iodine/iodine_connection.h +55 -0
- data/ext/iodine/iodine_defer.c +391 -0
- data/ext/iodine/iodine_defer.h +7 -0
- data/ext/iodine/{rb-fiobj2rb.h → iodine_fiobj2rb.h} +6 -6
- data/ext/iodine/iodine_helpers.c +51 -5
- data/ext/iodine/iodine_helpers.h +2 -3
- data/ext/iodine/iodine_http.c +284 -141
- data/ext/iodine/iodine_http.h +2 -2
- data/ext/iodine/iodine_json.c +13 -13
- data/ext/iodine/iodine_json.h +1 -1
- data/ext/iodine/iodine_pubsub.c +573 -823
- data/ext/iodine/iodine_pubsub.h +15 -27
- data/ext/iodine/{rb-rack-io.c → iodine_rack_io.c} +30 -8
- data/ext/iodine/{rb-rack-io.h → iodine_rack_io.h} +1 -0
- data/ext/iodine/iodine_store.c +136 -0
- data/ext/iodine/iodine_store.h +20 -0
- data/ext/iodine/iodine_tcp.c +385 -0
- data/ext/iodine/iodine_tcp.h +9 -0
- data/lib/iodine.rb +73 -171
- data/lib/iodine/connection.rb +34 -0
- data/lib/iodine/pubsub.rb +5 -18
- data/lib/iodine/rack_utils.rb +43 -0
- data/lib/iodine/version.rb +1 -1
- data/lib/rack/handler/iodine.rb +1 -182
- metadata +17 -18
- data/ext/iodine/iodine_protocol.c +0 -689
- data/ext/iodine/iodine_protocol.h +0 -13
- data/ext/iodine/iodine_websockets.c +0 -550
- data/ext/iodine/iodine_websockets.h +0 -17
- data/ext/iodine/rb-call.c +0 -156
- data/ext/iodine/rb-call.h +0 -70
- data/ext/iodine/rb-defer.c +0 -124
- data/ext/iodine/rb-registry.c +0 -150
- data/ext/iodine/rb-registry.h +0 -34
- data/lib/iodine/cli.rb +0 -89
- data/lib/iodine/monkeypatch.rb +0 -46
- data/lib/iodine/protocol.rb +0 -42
- data/lib/iodine/websocket.rb +0 -16
data/lib/iodine.rb
CHANGED
@@ -3,215 +3,118 @@ require 'socket'
|
|
3
3
|
require 'iodine/version'
|
4
4
|
require 'iodine/iodine'
|
5
5
|
|
6
|
-
# Iodine is
|
6
|
+
# Iodine is an HTTP / WebSocket server as well as an Evented Network Tool Library.
|
7
7
|
#
|
8
|
-
# Here is a
|
8
|
+
# Here is a simple telnet based echo server using Iodine (see full list at {Iodine::Connection}):
|
9
9
|
#
|
10
10
|
#
|
11
|
+
# require 'iodine'
|
11
12
|
# # define the protocol for our service
|
12
|
-
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
13
|
+
# module EchoProtocol
|
14
|
+
# def on_open(client)
|
15
|
+
# # Set a connection timeout
|
16
|
+
# client.timeout = 10
|
17
|
+
# # Write a welcome message
|
18
|
+
# client.write "Echo server running on Iodine #{Iodine::VERSION}.\r\n"
|
19
|
+
# end
|
20
|
+
# # this is called for incoming data - note data might be fragmented.
|
21
|
+
# def on_message(client, data)
|
16
22
|
# # write the data we received
|
17
|
-
# write "echo: #{
|
23
|
+
# client.write "echo: #{data}"
|
18
24
|
# # close the connection when the time comes
|
19
|
-
# close if
|
25
|
+
# client.close if data =~ /^bye[\n\r]/
|
26
|
+
# end
|
27
|
+
# # called if the connection is still open and the server is shutting down.
|
28
|
+
# def on_shutdown(client)
|
29
|
+
# # write the data we received
|
30
|
+
# client.write "Server going away\r\n"
|
20
31
|
# end
|
32
|
+
# extend self
|
21
33
|
# end
|
22
|
-
# # create the service instance
|
23
|
-
# Iodine.listen 3000
|
34
|
+
# # create the service instance, the block returns a connection handler.
|
35
|
+
# Iodine.listen(port: "3000") { EchoProtocol }
|
24
36
|
# # start the service
|
37
|
+
# Iodine.threads = 1
|
25
38
|
# Iodine.start
|
26
39
|
#
|
27
40
|
#
|
28
|
-
# Please read the {file:README.md} file for an introduction to Iodine and an overview of it's API.
|
29
|
-
#
|
30
|
-
# == The API
|
31
|
-
#
|
32
|
-
# The main API methods for the top {Iodine} namesapce are grouped here by subject.
|
33
|
-
#
|
34
|
-
# === Event Loop / Concurrency
|
35
|
-
#
|
36
|
-
# Iodine manages an internal event-loop and reactor pattern. The following API
|
37
|
-
# manages Iodine's behavior.
|
38
|
-
#
|
39
|
-
# * {Iodine.threads}, {Iodine.threads=} gets or sets the amount of threads iodine will use in it's working thread pool.
|
40
|
-
# * {Iodine.processes}, {Iodine.processes} gets or sets the amount of processes iodine will utilize (`fork`) to handle connections.
|
41
|
-
# * {Iodine.start} starts iodine's event loop and reactor pattern. At this point, it's impossible to change the number of threads or processes used.
|
42
|
-
#
|
43
|
-
# === Event and Task Scheduling
|
44
|
-
#
|
45
|
-
# * {Iodine.run} schedules a block of code to run asynchronously.
|
46
|
-
# * {Iodine.run_after}, {Iodine.run_every} schedules a block of code to run (asynchronously) using a timer.
|
47
|
-
# * {Iodine.start} starts iodine's event loop and reactor pattern. At this point, it's impossible to change the number of threads or processes used.
|
48
|
-
#
|
49
|
-
# In addition to the top level API, there's also the connection class and connection instance API, as specified in the {Iodine::Protocol} and {Iodine::Websocket} documentation, which allows for a connection bound task(s) to be scheduled to run within the connection's lock (for example, {Iodine::Websocket#defer} and {Iodine::Websocket#each}).
|
50
|
-
#
|
51
|
-
# === Connection Handling
|
52
|
-
#
|
53
|
-
# Iodine handles connections using {Iodine::Protocol} objects. The following API
|
54
|
-
# manages either built-in or custom {Protocol} objects (classes / instances) in relation to their network sockets.
|
55
|
-
#
|
56
|
-
# * {Iodine.attach_fd}, {Iodine.attach_io} allows Iodine to take controll of an IO object (i.e., a TCP/IP Socket, a Unix Socket or a pipe).
|
57
|
-
# * {Iodine.connect} creates a new TCP/IP connection using the specified Protocol.
|
58
|
-
# * {Iodine.listen} listens to new TCP/IP connections using the specified Protocol.
|
59
|
-
# * {Iodine.listen2http} listens to new TCP/IP connections using the buildin HTTP / Websocket Protocol.
|
60
|
-
# * {Iodine.warmup} warms up any HTTP Rack applications.
|
61
|
-
# * {Iodine.count} counts the number of connections (including HTTP / Websocket connections).
|
62
|
-
#
|
63
|
-
# In addition to the top level API, there's also the connection class and connection instance API, as specified in the {Iodine::Protocol} and {Iodine::Websocket} documentation.
|
64
41
|
#
|
65
|
-
#
|
42
|
+
# Methods for setting up and starting {Iodine} include {start}, {threads}, {threads=}, {workers} and {workers=}.
|
66
43
|
#
|
67
|
-
#
|
44
|
+
# Methods for setting startup / operational callbacks include {on_idle}, {on_shutdown}, {before_fork} and {after_fork}.
|
68
45
|
#
|
69
|
-
#
|
46
|
+
# Methods for asynchronous execution include {run} (same as {defer}), {run_after} and {run_every}.
|
70
47
|
#
|
71
|
-
#
|
72
|
-
# * {Iodine.publish} publishes a message to a Pub/Sub channel. The message will be sent to all subscribers - connections, other processes in the cluster and possibly other machines (when using an engine such as {Iodine::PubSub::RedisEngine}).
|
73
|
-
# * {Iodine::PubSub.default_engine=}, {Iodine::PubSub.default_engine} sets or gets the default Pub/Sub {Iodine::PubSub::Engine}. i.e., when set to a new {Iodine::PubSub::RedisEngine} instance, all Pub/Sub method calls will use the Redis engine (unless explicitly requiring a different engine).
|
48
|
+
# Methods for application wide pub/sub include {subscribe}, {unsubscribe} and {publish}. Connection specific pub/sub methods are documented in the {Iodine::Connection} class).
|
74
49
|
#
|
75
|
-
#
|
50
|
+
# Methods for TCP/IP and Unix Sockets connections include {listen} and {connect}.
|
76
51
|
#
|
77
|
-
#
|
52
|
+
# Methods for HTTP connections include {listen2http}.
|
78
53
|
#
|
79
|
-
#
|
54
|
+
# Note that the HTTP server supports both TCP/IP and Unix Sockets as well as SSE / WebSockets extensions.
|
80
55
|
#
|
81
|
-
#
|
56
|
+
# Iodine doesn't call {patch_rack} automatically, but doing so will improve Rack's performace.
|
82
57
|
#
|
83
|
-
#
|
58
|
+
# Please read the {file:README.md} file for an introduction to Iodine.
|
84
59
|
#
|
85
|
-
# require 'iodine'
|
86
|
-
# require 'rack'
|
87
|
-
# # a String in need of decoding
|
88
|
-
# s = '%E3%83%AB%E3%83%93%E3%82%A4%E3%82%B9%E3%81%A8'
|
89
|
-
# Benchmark.bm do |bm|
|
90
|
-
# # Pre-Patch
|
91
|
-
# bm.report(" Rack.unescape") {1_000_000.times { Rack::Utils.unescape s } }
|
92
|
-
# bm.report(" Rack.rfc2822") {1_000_000.times { Rack::Utils.rfc2822(Time.now) } }
|
93
|
-
# bm.report(" Rack.rfc2109") {1_000_000.times { Rack::Utils.rfc2109(Time.now) } }
|
94
|
-
# # Perform Patch
|
95
|
-
# Iodine.patch_rack
|
96
|
-
# puts " --- Monkey Patching Rack ---"
|
97
|
-
# # Post Patch
|
98
|
-
# bm.report("Patched.unescape") {1_000_000.times { Rack::Utils.unescape s } }
|
99
|
-
# bm.report(" Patched.rfc2822") {1_000_000.times { Rack::Utils.rfc2822(Time.now) } }
|
100
|
-
# bm.report(" Patched.rfc2109") {1_000_000.times { Rack::Utils.rfc2109(Time.now) } }
|
101
|
-
# end && nil
|
102
|
-
#
|
103
|
-
# Results:
|
104
|
-
# user system total real
|
105
|
-
# Rack.unescape 8.660000 0.010000 8.670000 ( 8.687807)
|
106
|
-
# Rack.rfc2822 3.730000 0.000000 3.730000 ( 3.727732)
|
107
|
-
# Rack.rfc2109 3.020000 0.010000 3.030000 ( 3.031940)
|
108
|
-
# --- Monkey Patching Rack ---
|
109
|
-
# Patched.unescape 0.340000 0.000000 0.340000 ( 0.341506)
|
110
|
-
# Patched.rfc2822 0.740000 0.000000 0.740000 ( 0.737796)
|
111
|
-
# Patched.rfc2109 0.690000 0.010000 0.700000 ( 0.700155)
|
112
|
-
#
|
113
|
-
# At the moment, the extent of the monkey patching offered is very limited.
|
114
|
-
# As new optimizations are added, the policy regarding monkey patching (benifit vs. risks) might be re-evaluated.
|
115
60
|
module Iodine
|
116
|
-
@threads = (ARGV.index('-t') && ARGV[ARGV.index('-t') + 1]) || ENV['MAX_THREADS']
|
117
|
-
@processes = (ARGV.index('-w') && ARGV[ARGV.index('-w') + 1]) || ENV['MAX_WORKERS']
|
118
|
-
@threads = @threads.to_i if @threads
|
119
|
-
if @processes == 'auto'
|
120
|
-
begin
|
121
|
-
require 'etc'
|
122
|
-
@processes = Etc.nprocessors
|
123
|
-
rescue Exception
|
124
|
-
warn "This version of Ruby doesn't support automatic CPU core detection."
|
125
|
-
end
|
126
|
-
end
|
127
|
-
@processes = @processes.to_i if @processes
|
128
|
-
|
129
|
-
# Get/Set the number of threads used in the thread pool (a static thread pool). Can be 1 (single working thread, the main thread will sleep) and can be 0 (the main thread will be used as the only active thread).
|
130
|
-
def self.threads
|
131
|
-
@threads
|
132
|
-
end
|
133
|
-
|
134
|
-
# Get/Set the number of threads used in the thread pool (a static thread pool). Can be 1 (single working thread, the main thread will sleep) and can be 0 (the main thread will be used as the only active thread).
|
135
|
-
def self.threads=(count)
|
136
|
-
@threads = count.to_i
|
137
|
-
end
|
138
|
-
|
139
|
-
# Get/Set the number of worker processes. A value greater then 1 will cause the Iodine to "fork" any extra worker processes needed.
|
140
|
-
def self.processes
|
141
|
-
@processes
|
142
|
-
end
|
143
|
-
|
144
|
-
# Get/Set the number of worker processes. A value greater then 1 will cause the Iodine to "fork" any extra worker processes needed.
|
145
|
-
def self.processes=(count)
|
146
|
-
@processes = count.to_i
|
147
|
-
end
|
148
|
-
|
149
|
-
# Runs the warmup sequence. {warmup} attempts to get every "autoloaded" (lazy loaded)
|
150
|
-
# file to load now instead of waiting for "first access". This allows multi-threaded safety and better memory utilization during forking.
|
151
|
-
#
|
152
|
-
# However, `warmup` might cause undefined behavior and should be avoided when using gems that initiate network / database connections or gems that spawn threads (i.e., ActiveRecord / ActiveCable).
|
153
|
-
#
|
154
|
-
# Use {warmup} when either {processes} or {threads} are set to more then 1 and gems don't spawn threads or initialize network connections.
|
155
|
-
def self.warmup app
|
156
|
-
# load anything marked with `autoload`, since autoload isn't thread safe nor fork friendly.
|
157
|
-
Iodine.run do
|
158
|
-
Module.constants.each do |n|
|
159
|
-
begin
|
160
|
-
Object.const_get(n)
|
161
|
-
rescue Exception => _e
|
162
|
-
end
|
163
|
-
end
|
164
|
-
::Rack::Builder.new(app) do |r|
|
165
|
-
r.warmup do |a|
|
166
|
-
client = ::Rack::MockRequest.new(a)
|
167
|
-
client.get('/')
|
168
|
-
end
|
169
|
-
end
|
170
|
-
end
|
171
|
-
end
|
172
61
|
|
173
62
|
# Will monkey patch some Rack methods to increase their performance.
|
63
|
+
#
|
64
|
+
# This is recommended, see {Iodine::Rack::Utils} for details.
|
174
65
|
def self.patch_rack
|
175
66
|
::Rack::Utils.class_eval do
|
176
67
|
Iodine::Base::MonkeyPatch::RackUtils.methods(false).each do |m|
|
177
68
|
::Rack::Utils.define_singleton_method(m,
|
178
69
|
Iodine::Base::MonkeyPatch::RackUtils.instance_method(m) )
|
70
|
+
end
|
179
71
|
end
|
180
72
|
end
|
181
|
-
end
|
182
73
|
|
183
|
-
|
184
|
-
def self.patch_json
|
185
|
-
::JSON.class_eval do
|
186
|
-
::JSON.define_singleton_method(:parse,
|
187
|
-
Iodine::JSON.instance_method(:parse) )
|
188
|
-
end
|
189
|
-
end
|
74
|
+
end
|
190
75
|
|
76
|
+
require 'rack/handler/iodine' unless defined? ::Iodine::Rack::IODINE_RACK_LOADED
|
191
77
|
|
192
|
-
@after_fork_blocks = []
|
193
|
-
# Performs a block of code whenever a new worker process spins up (performed once per worker).
|
194
|
-
def self.after_fork(*args, &block)
|
195
|
-
if(block)
|
196
|
-
@after_fork_blocks << [args, block]
|
197
|
-
else
|
198
|
-
@after_fork_blocks.each {|b| b[1].call(b[0]) }
|
199
|
-
end
|
200
|
-
end
|
201
78
|
|
202
|
-
|
203
|
-
# Performs a block of code just before a new worker process spins up (performed once per worker).
|
204
|
-
def self.before_fork(*args, &block)
|
205
|
-
if(block)
|
206
|
-
@before_fork_blocks << [args, block]
|
207
|
-
else
|
208
|
-
@before_fork_blocks.each {|b| b[1].call(b[0]) }
|
209
|
-
end
|
210
|
-
end
|
79
|
+
### CLI argument parsing
|
211
80
|
|
212
|
-
|
81
|
+
if ARGV.index('-b') && ARGV[ARGV.index('-b') + 1]
|
82
|
+
Iodine::DEFAULT_HTTP_ARGS[:address] = ARGV[ARGV.index('-b') + 1]
|
83
|
+
end
|
84
|
+
if ARGV.index('-p') && ARGV[ARGV.index('-p') + 1]
|
85
|
+
Iodine::DEFAULT_HTTP_ARGS[:port] = ARGV[ARGV.index('-p') + 1]
|
213
86
|
end
|
214
87
|
|
88
|
+
if ARGV.index('-maxbd') && ARGV[ARGV.index('-maxbd') + 1]
|
89
|
+
Iodine::DEFAULT_HTTP_ARGS[:max_body_size] = ARGV[ARGV.index('-maxbd') + 1].to_i
|
90
|
+
end
|
91
|
+
if ARGV.index('-maxms') && ARGV[ARGV.index('-maxms') + 1]
|
92
|
+
Iodine::DEFAULT_HTTP_ARGS[:max_msg_size] = ARGV[ARGV.index('-maxms') + 1].to_i
|
93
|
+
end
|
94
|
+
if ARGV.index('-maxhead') && ARGV[ARGV.index('-maxhead') + 1] && ARGV[ARGV.index('-maxhead') + 1].to_i > 0
|
95
|
+
Iodine::DEFAULT_HTTP_ARGS[:max_headers] = ARGV[ARGV.index('-maxhead') + 1].to_i
|
96
|
+
end
|
97
|
+
if ARGV.index('-ping') && ARGV[ARGV.index('-ping') + 1]
|
98
|
+
Iodine::DEFAULT_HTTP_ARGS[:ping] = ARGV[ARGV.index('-ping') + 1].to_i
|
99
|
+
end
|
100
|
+
if ARGV.index('-www') && ARGV[ARGV.index('-www') + 1]
|
101
|
+
Iodine::DEFAULT_HTTP_ARGS[:public] = ARGV[ARGV.index('-www') + 1]
|
102
|
+
end
|
103
|
+
if ARGV.index('-tout') && ARGV[ARGV.index('-tout') + 1]
|
104
|
+
Iodine::DEFAULT_HTTP_ARGS[:timeout] = ARGV[ARGV.index('-tout') + 1].to_i
|
105
|
+
puts "WARNNING: timeout set to 0 (ignored, timeout will be ~5 seconds)." if (Iodine::DEFAULT_HTTP_ARGS[:timeout].to_i <= 0 || Iodine::DEFAULT_HTTP_ARGS[:timeout].to_i > 255)
|
106
|
+
end
|
107
|
+
Iodine::DEFAULT_HTTP_ARGS[:log] = true if ARGV.index('-v')
|
108
|
+
|
109
|
+
if ARGV.index('-t') && ARGV[ARGV.index('-t') + 1].to_i != 0
|
110
|
+
Iodine.threads = ARGV[ARGV.index('-t') + 1].to_i
|
111
|
+
end
|
112
|
+
if ARGV.index('-w') && ARGV[ARGV.index('-w') + 1].to_i != 0
|
113
|
+
Iodine.workers = ARGV[ARGV.index('-w') + 1].to_i
|
114
|
+
end
|
115
|
+
|
116
|
+
### Puma / Thin DSL compatibility
|
117
|
+
|
215
118
|
if(!defined?(after_fork))
|
216
119
|
# Performs a block of code whenever a new worker process spins up (performed once per worker).
|
217
120
|
def after_fork(*args, &block)
|
@@ -232,4 +135,3 @@ if(!defined?(before_fork))
|
|
232
135
|
end
|
233
136
|
|
234
137
|
|
235
|
-
require 'rack/handler/iodine' unless defined? ::Iodine::Rack::IODINE_RACK_LOADED
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Iodine
|
2
|
+
# Iodine's {Iodine::Connection} class is the class that TCP/IP, WebSockets and SSE connections inherit from.
|
3
|
+
#
|
4
|
+
# Instances of this class are passed to the callback objects. i.e.:
|
5
|
+
#
|
6
|
+
# module MyConnectionCallbacks
|
7
|
+
# # called when the callback object is linked with a new client
|
8
|
+
# def on_open client
|
9
|
+
# client.is_a?(Iodine::Connection) # => true
|
10
|
+
# end
|
11
|
+
# # called when data is available
|
12
|
+
# def on_message client, data
|
13
|
+
# client.is_a?(Iodine::Connection) # => true
|
14
|
+
# end
|
15
|
+
# # called when the server is shutting down, before closing the client
|
16
|
+
# # (it's still possible to send messages to the client)
|
17
|
+
# def on_shutdown client
|
18
|
+
# client.is_a?(Iodine::Connection) # => true
|
19
|
+
# end
|
20
|
+
# # called when the client is closed (no longer available)
|
21
|
+
# def on_close client
|
22
|
+
# client.is_a?(Iodine::Connection) # => true
|
23
|
+
# end
|
24
|
+
# # called when all the previous calls to `client.write` have completed
|
25
|
+
# # (the local buffer was drained and is now empty)
|
26
|
+
# def on_drained client
|
27
|
+
# client.is_a?(Iodine::Connection) # => true
|
28
|
+
# end
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# All connection related actions can be performed using the methods provided through this class.
|
32
|
+
class Connection
|
33
|
+
end
|
34
|
+
end
|
data/lib/iodine/pubsub.rb
CHANGED
@@ -26,9 +26,9 @@ module Iodine
|
|
26
26
|
# Iodine comes with two built-in engines:
|
27
27
|
#
|
28
28
|
# * `Iodine::PubSub::Engine::CLUSTER` will distribute messages to all subscribers in the process cluster.
|
29
|
-
# * `Iodine::PubSub::Engine::
|
29
|
+
# * `Iodine::PubSub::Engine::PROCESS` will distribute messages to all subscribers sharing the same process.
|
30
30
|
#
|
31
|
-
# {Iodine::PubSub::Engine} instances
|
31
|
+
# It's recommended that {Iodine::PubSub::Engine} instances be initialized only after Iodine
|
32
32
|
# started running (or the `fork`ing of the engine's connection will introduce communication issues).
|
33
33
|
#
|
34
34
|
# For this reason, the best approcah to initialization would be:
|
@@ -41,28 +41,15 @@ module Iodine
|
|
41
41
|
# MyEngine = MyEngineClass.new
|
42
42
|
# end
|
43
43
|
#
|
44
|
-
# {Iodine::PubSub::Engine} child classes MUST override the {Iodine::PubSub::Engine#subscribe},
|
45
|
-
# {Iodine::PubSub::Engine#unsubscribe} and {Iodine::PubSub::Engine#publish}
|
44
|
+
# {Iodine::PubSub::Engine} child classes MUST override the {Iodine::PubSub::Engine#subscribe}, {Iodine::PubSub::Engine#unsubscribe} and {Iodine::PubSub::Engine#publish}
|
46
45
|
# in order to perform this actions using the backend service (i.e. using Redis).
|
47
46
|
#
|
48
47
|
# Once an {Iodine::PubSub::Engine} instance receives a message from the backend service,
|
49
|
-
# it should forward the message to the Iodine distribution layer using the {Iodine
|
48
|
+
# it should forward the message to the Iodine distribution layer using the {Iodine.publish} method, setting the 3rd argument to `false`.
|
50
49
|
#
|
51
|
-
# Iodine will than distribute the message to all registered clients.
|
52
|
-
#
|
53
|
-
# *IMPORTANT*:
|
54
|
-
#
|
55
|
-
# Connections shouldn't call any of the {Iodine::PubSub::Engine} or {Iodine::PubSub} methods directly,
|
56
|
-
# since connections might be disconnected at any time, at which point their memory will become corrupted or recycled.
|
57
|
-
#
|
58
|
-
# The connection pub/sub methods (coming soon) keep the application safe from these
|
59
|
-
# risks by performing specific checks for connection related pub/sub actions.
|
50
|
+
# Iodine will than distribute the message to all registered clients in that specific process (if the engine is cluster wide, set the 3rd argument to {Iodine::PubSub::CLUSTER}.
|
60
51
|
#
|
61
52
|
class Engine
|
62
53
|
end
|
63
|
-
# This is the (currently) default pub/sub engine. It will distribute messages to all subscribers in the process cluster.
|
64
|
-
CLUSTER
|
65
|
-
# This is a single process pub/sub engine. It will distribute messages to all subscribers sharing the same process.
|
66
|
-
SINGLE_PROCESS
|
67
54
|
end
|
68
55
|
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Iodine
|
2
|
+
# Iodine's {Iodine::Rack} module provides a Rack complient interface (connecting Iodine to Rack) for an HTTP and Websocket Server.
|
3
|
+
module Rack
|
4
|
+
# The methods in this module are designed to be Rack compatible and to provide higher performance than the original Rack methods.
|
5
|
+
#
|
6
|
+
# Iodine does NOT monkey patch Rack automatically. However, it's possible and recommended to moneky patch Rack::Utils to use the methods in this module.
|
7
|
+
#
|
8
|
+
# Choosing to monkey patch Rack::Utils could offer significant performance gains for some applications. i.e. (on my machine):
|
9
|
+
#
|
10
|
+
# require 'iodine'
|
11
|
+
# require 'rack'
|
12
|
+
# # a String in need of decoding
|
13
|
+
# s = '%E3%83%AB%E3%83%93%E3%82%A4%E3%82%B9%E3%81%A8'
|
14
|
+
# Benchmark.bm do |bm|
|
15
|
+
# # Pre-Patch
|
16
|
+
# bm.report(" Rack.unescape") {1_000_000.times { Rack::Utils.unescape s } }
|
17
|
+
# bm.report(" Rack.rfc2822") {1_000_000.times { Rack::Utils.rfc2822(Time.now) } }
|
18
|
+
# bm.report(" Rack.rfc2109") {1_000_000.times { Rack::Utils.rfc2109(Time.now) } }
|
19
|
+
# # Perform Patch
|
20
|
+
# Iodine.patch_rack
|
21
|
+
# puts " --- Monkey Patching Rack ---"
|
22
|
+
# # Post Patch
|
23
|
+
# bm.report("Patched.unescape") {1_000_000.times { Rack::Utils.unescape s } }
|
24
|
+
# bm.report(" Patched.rfc2822") {1_000_000.times { Rack::Utils.rfc2822(Time.now) } }
|
25
|
+
# bm.report(" Patched.rfc2109") {1_000_000.times { Rack::Utils.rfc2109(Time.now) } }
|
26
|
+
# end && nil
|
27
|
+
#
|
28
|
+
# Results:
|
29
|
+
#
|
30
|
+
# user system total real
|
31
|
+
# Rack.unescape 8.706881 0.019995 8.726876 ( 8.740530)
|
32
|
+
# Rack.rfc2822 3.270305 0.007519 3.277824 ( 3.279416)
|
33
|
+
# Rack.rfc2109 3.152188 0.003852 3.156040 ( 3.157975)
|
34
|
+
# --- Monkey Patching Rack ---
|
35
|
+
# Patched.unescape 0.327231 0.003125 0.330356 ( 0.337090)
|
36
|
+
# Patched.rfc2822 0.691304 0.003330 0.694634 ( 0.701172)
|
37
|
+
# Patched.rfc2109 0.685029 0.001956 0.686985 ( 0.687607)
|
38
|
+
#
|
39
|
+
# Iodine uses the same code internally for HTTP timestamping (adding missing `Date` headers) and logging.
|
40
|
+
module Utils
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/lib/iodine/version.rb
CHANGED
data/lib/rack/handler/iodine.rb
CHANGED
@@ -3,202 +3,21 @@ require 'iodine' unless defined?(::Iodine::VERSION)
|
|
3
3
|
module Iodine
|
4
4
|
# Iodine's {Iodine::Rack} module provides a Rack complient interface (connecting Iodine to Rack) for an HTTP and Websocket Server.
|
5
5
|
module Rack
|
6
|
-
# get/set the Rack application.
|
7
|
-
def self.app=(val)
|
8
|
-
@app = val
|
9
|
-
end
|
10
|
-
|
11
|
-
# get/set the Rack application.
|
12
|
-
def self.app
|
13
|
-
@app
|
14
|
-
end
|
15
|
-
@app = nil
|
16
|
-
|
17
|
-
# get/set the HTTP connection timeout property. Defaults to 40. Limited to a maximum of 255. 0 values are silently ignored.
|
18
|
-
def self.timeout=(t)
|
19
|
-
@timeout = t
|
20
|
-
end
|
21
|
-
|
22
|
-
# get/set the HTTP connection timeout property. Defaults to 40 seconds.
|
23
|
-
def self.timeout
|
24
|
-
@timeout
|
25
|
-
end
|
26
|
-
@timeout = 0
|
27
|
-
|
28
|
-
# get/set the Websocket connection timeout property. Defaults to 40 seconds. Limited to a maximum of 255. 0 values are silently ignored.
|
29
|
-
def self.ws_timeout=(t)
|
30
|
-
@ws_timeout = t
|
31
|
-
end
|
32
|
-
|
33
|
-
# get/set the Websocket connection timeout property. Defaults to 40 seconds. Limited to a maximum of 255. 0 values are silently ignored.
|
34
|
-
def self.ws_timeout
|
35
|
-
@ws_timeout
|
36
|
-
end
|
37
|
-
@ws_timeout = 0
|
38
|
-
|
39
|
-
# get/set the HTTP public folder property. Defaults to the incoming arguments or `nil`.
|
40
|
-
def self.public=(val)
|
41
|
-
@public = val
|
42
|
-
end
|
43
|
-
@public = nil
|
44
|
-
|
45
|
-
# get/set the HTTP public folder property. Defaults to `nil`.
|
46
|
-
def self.public
|
47
|
-
@public
|
48
|
-
end
|
49
|
-
|
50
|
-
# get/set the maximum HTTP body size for incoming data. Defaults to ~50Mb. 0 values are silently ignored.
|
51
|
-
def self.max_body_size=(val)
|
52
|
-
@max_body = val
|
53
|
-
end
|
54
|
-
|
55
|
-
# get/set the maximum HTTP body size for incoming data. Defaults to ~50Mb.
|
56
|
-
def self.max_body_size
|
57
|
-
@max_body
|
58
|
-
end
|
59
|
-
# get/set the maximum HTTP body size for incoming data. Defaults to ~50Mb. 0 values are silently ignored.
|
60
|
-
def self.max_body=(val)
|
61
|
-
@max_body = val
|
62
|
-
end
|
63
|
-
|
64
|
-
# get/set the maximum HTTP body size for incoming data. Defaults to ~50Mb.
|
65
|
-
def self.max_body
|
66
|
-
@max_body
|
67
|
-
end
|
68
|
-
@max_body = 0
|
69
|
-
|
70
|
-
# get/set the maximum HTTP header length for incoming requests. Defaults to ~32Kb. 0 values are silently ignored.
|
71
|
-
def self.max_headers=(val)
|
72
|
-
@max_headers = val
|
73
|
-
end
|
74
|
-
|
75
|
-
# get/set the maximum HTTP header length for incoming requests. Defaults to ~32Kb.
|
76
|
-
def self.max_headers
|
77
|
-
@max_headers
|
78
|
-
end
|
79
|
-
@max_headers = 0
|
80
|
-
|
81
|
-
|
82
|
-
# get/set the maximum Websocket body size for incoming data. Defaults to defaults to ~250KB. 0 values are silently ignored.
|
83
|
-
def self.max_msg_size=(val)
|
84
|
-
@max_msg = val
|
85
|
-
end
|
86
|
-
|
87
|
-
# get/set the maximum Websocket body size for incoming data. Defaults to defaults to ~250KB. 0 values are silently ignored.
|
88
|
-
def self.max_msg_size
|
89
|
-
@max_msg
|
90
|
-
end
|
91
|
-
|
92
|
-
# get/set the maximum Websocket body size for incoming data. Defaults to defaults to ~250KB. 0 values are silently ignored.
|
93
|
-
def self.max_msg=(val)
|
94
|
-
@max_msg = val
|
95
|
-
end
|
96
|
-
|
97
|
-
# get/set the maximum Websocket body size for incoming data. Defaults to defaults to ~250KB. 0 values are silently ignored.
|
98
|
-
def self.max_msg
|
99
|
-
@max_msg
|
100
|
-
end
|
101
|
-
@max_msg = 0
|
102
|
-
|
103
|
-
# get/set the HTTP logging value (true / false). Defaults to the incoming argumrnts or `false`.
|
104
|
-
def self.log=(val)
|
105
|
-
@log = val
|
106
|
-
end
|
107
|
-
|
108
|
-
# get/set the HTTP logging value (true / false). Defaults to the incoming argumrnts or `false`.
|
109
|
-
def self.log
|
110
|
-
@log
|
111
|
-
end
|
112
|
-
@log = false
|
113
|
-
|
114
|
-
# get/set the HTTP listening port. Defaults to 3000.
|
115
|
-
def self.port=(val)
|
116
|
-
@port = val
|
117
|
-
end
|
118
|
-
|
119
|
-
# get/set the HTTP listening port. Defaults to 3000.
|
120
|
-
def self.port
|
121
|
-
@port
|
122
|
-
end
|
123
|
-
@port = 3000.to_s
|
124
|
-
|
125
|
-
# get/set the HTTP socket binding address. Defaults to `nil` (usually best).
|
126
|
-
def self.address=(val)
|
127
|
-
@address = val
|
128
|
-
end
|
129
|
-
|
130
|
-
# get/set the HTTP socket binding address. Defaults to `nil` (usually best).
|
131
|
-
def self.address
|
132
|
-
@address
|
133
|
-
end
|
134
|
-
@address = nil
|
135
6
|
|
136
7
|
# Runs a Rack app, as par the Rack handler requirements.
|
137
8
|
def self.run(app, options = {})
|
138
9
|
# nested applications... is that a thing?
|
139
|
-
|
140
|
-
old_app = @app
|
141
|
-
@app = proc do |env|
|
142
|
-
ret = old_app.call(env)
|
143
|
-
ret = app.call(env) if !ret.is_a?(Array) || (ret.is_a?(Array) && ret[0].to_i == 404)
|
144
|
-
ret
|
145
|
-
end
|
146
|
-
else
|
147
|
-
@app = app
|
148
|
-
end
|
149
|
-
@port = options[:Port].to_s if options[:Port]
|
150
|
-
@address = options[:Address].to_s if options[:Address]
|
151
|
-
|
152
|
-
# # provide Websocket features using Rack::Websocket
|
153
|
-
# Rack.send :remove_const, :Websocket if defined?(Rack::Websocket)
|
154
|
-
# Rack.const_set :Websocket, ::Iodine::Websocket
|
10
|
+
Iodine.listen2http(app: app, port: options[:Port], address: options[:Address])
|
155
11
|
|
156
12
|
# start Iodine
|
157
13
|
Iodine.start
|
158
14
|
|
159
|
-
# # remove the Websocket features from Rack::Websocket
|
160
|
-
# Rack.send :remove_const, :Websocket
|
161
|
-
|
162
15
|
true
|
163
16
|
end
|
164
17
|
IODINE_RACK_LOADED = true
|
165
18
|
end
|
166
19
|
end
|
167
20
|
|
168
|
-
if ARGV.index('-b') && ARGV[ARGV.index('-b') + 1]
|
169
|
-
Iodine::Rack.address = ARGV[ARGV.index('-b') + 1]
|
170
|
-
end
|
171
|
-
if ARGV.index('-p') && ARGV[ARGV.index('-p') + 1]
|
172
|
-
Iodine::Rack.port = ARGV[ARGV.index('-p') + 1]
|
173
|
-
end
|
174
|
-
|
175
|
-
if ARGV.index('-maxbd') && ARGV[ARGV.index('-maxbd') + 1]
|
176
|
-
Iodine::Rack.max_body_size = ARGV[ARGV.index('-maxbd') + 1].to_i
|
177
|
-
end
|
178
|
-
if ARGV.index('-maxms') && ARGV[ARGV.index('-maxms') + 1]
|
179
|
-
Iodine::Rack.max_msg_size = ARGV[ARGV.index('-maxms') + 1].to_i
|
180
|
-
end
|
181
|
-
if ARGV.index('-ping') && ARGV[ARGV.index('-ping') + 1]
|
182
|
-
Iodine::Rack.ws_timeout = ARGV[ARGV.index('-ping') + 1].to_i
|
183
|
-
end
|
184
|
-
if ARGV.index('-www') && ARGV[ARGV.index('-www') + 1]
|
185
|
-
Iodine::Rack.public = ARGV[ARGV.index('-www') + 1]
|
186
|
-
end
|
187
|
-
if ARGV.index('-maxhead') && ARGV[ARGV.index('-maxhead') + 1]
|
188
|
-
Iodine::Rack.max_headers = ARGV[ARGV.index('-maxhead') + 1].to_i
|
189
|
-
end
|
190
|
-
if ARGV.index('-tout') && ARGV[ARGV.index('-tout') + 1]
|
191
|
-
Iodine::Rack.timeout = ARGV[ARGV.index('-tout') + 1].to_i
|
192
|
-
puts "WARNNING: Iodine::Rack.timeout set to 0 (ignored, timeout will be ~5 seconds)."
|
193
|
-
end
|
194
|
-
Iodine::Rack.log = true if ARGV.index('-v')
|
195
|
-
Iodine::Rack.log = false if ARGV.index('-q')
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
# Iodine::Rack.app = proc { |env| p env; puts env['rack.input'].read(1024).tap { |s| puts "Got data #{s.length} long, #{s[0].ord}, #{s[1].ord} ... #{s[s.length - 2].ord}, #{s[s.length - 1].ord}:" if s }; env['rack.input'].rewind; [404, {}, []] }
|
201
|
-
|
202
21
|
ENV['RACK_HANDLER'] = 'iodine'
|
203
22
|
|
204
23
|
# make Iodine the default fallback position for Rack.
|