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.

Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -0
  3. data/README.md +63 -100
  4. data/bin/raw-rbhttp +12 -7
  5. data/examples/config.ru +8 -7
  6. data/examples/echo.ru +8 -7
  7. data/examples/info.md +41 -35
  8. data/examples/pubsub_engine.ru +12 -12
  9. data/examples/redis.ru +10 -12
  10. data/examples/shootout.ru +19 -42
  11. data/exe/iodine +116 -1
  12. data/ext/iodine/defer.c +1 -1
  13. data/ext/iodine/facil.c +12 -8
  14. data/ext/iodine/facil.h +2 -2
  15. data/ext/iodine/iodine.c +177 -343
  16. data/ext/iodine/iodine.h +18 -72
  17. data/ext/iodine/iodine_caller.c +132 -0
  18. data/ext/iodine/iodine_caller.h +21 -0
  19. data/ext/iodine/iodine_connection.c +841 -0
  20. data/ext/iodine/iodine_connection.h +55 -0
  21. data/ext/iodine/iodine_defer.c +391 -0
  22. data/ext/iodine/iodine_defer.h +7 -0
  23. data/ext/iodine/{rb-fiobj2rb.h → iodine_fiobj2rb.h} +6 -6
  24. data/ext/iodine/iodine_helpers.c +51 -5
  25. data/ext/iodine/iodine_helpers.h +2 -3
  26. data/ext/iodine/iodine_http.c +284 -141
  27. data/ext/iodine/iodine_http.h +2 -2
  28. data/ext/iodine/iodine_json.c +13 -13
  29. data/ext/iodine/iodine_json.h +1 -1
  30. data/ext/iodine/iodine_pubsub.c +573 -823
  31. data/ext/iodine/iodine_pubsub.h +15 -27
  32. data/ext/iodine/{rb-rack-io.c → iodine_rack_io.c} +30 -8
  33. data/ext/iodine/{rb-rack-io.h → iodine_rack_io.h} +1 -0
  34. data/ext/iodine/iodine_store.c +136 -0
  35. data/ext/iodine/iodine_store.h +20 -0
  36. data/ext/iodine/iodine_tcp.c +385 -0
  37. data/ext/iodine/iodine_tcp.h +9 -0
  38. data/lib/iodine.rb +73 -171
  39. data/lib/iodine/connection.rb +34 -0
  40. data/lib/iodine/pubsub.rb +5 -18
  41. data/lib/iodine/rack_utils.rb +43 -0
  42. data/lib/iodine/version.rb +1 -1
  43. data/lib/rack/handler/iodine.rb +1 -182
  44. metadata +17 -18
  45. data/ext/iodine/iodine_protocol.c +0 -689
  46. data/ext/iodine/iodine_protocol.h +0 -13
  47. data/ext/iodine/iodine_websockets.c +0 -550
  48. data/ext/iodine/iodine_websockets.h +0 -17
  49. data/ext/iodine/rb-call.c +0 -156
  50. data/ext/iodine/rb-call.h +0 -70
  51. data/ext/iodine/rb-defer.c +0 -124
  52. data/ext/iodine/rb-registry.c +0 -150
  53. data/ext/iodine/rb-registry.h +0 -34
  54. data/lib/iodine/cli.rb +0 -89
  55. data/lib/iodine/monkeypatch.rb +0 -46
  56. data/lib/iodine/protocol.rb +0 -42
  57. data/lib/iodine/websocket.rb +0 -16
@@ -0,0 +1,9 @@
1
+ #ifndef H_IODINE_RAW_TCP_IP_H
2
+ #define H_IODINE_RAW_TCP_IP_H
3
+
4
+ #include "ruby.h"
5
+
6
+ void iodine_init_tcp_connections(void);
7
+ void iodine_tcp_attch_uuid(intptr_t uuid, VALUE handler);
8
+
9
+ #endif
@@ -3,215 +3,118 @@ require 'socket'
3
3
  require 'iodine/version'
4
4
  require 'iodine/iodine'
5
5
 
6
- # Iodine is both a Rack server and a platform for writing evented network services on Ruby.
6
+ # Iodine is an HTTP / WebSocket server as well as an Evented Network Tool Library.
7
7
  #
8
- # Here is a sample Echo server using Iodine:
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
- # class EchoProtocol
13
- # @timeout = 10
14
- # # this is just one possible callback with a recyclable buffer
15
- # def on_message buffer
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: #{buffer}"
23
+ # client.write "echo: #{data}"
18
24
  # # close the connection when the time comes
19
- # close if buffer =~ /^bye[\n\r]/
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, EchoProtocol
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
- # === Pub/Sub
42
+ # Methods for setting up and starting {Iodine} include {start}, {threads}, {threads=}, {workers} and {workers=}.
66
43
  #
67
- # Iodine offers a native Pub/Sub engine (no database required) that can be easily extended by implementing a Pub/Sub {Iodine::PubSub::Engine}.
44
+ # Methods for setting startup / operational callbacks include {on_idle}, {on_shutdown}, {before_fork} and {after_fork}.
68
45
  #
69
- # The following methods offect server side Pub/Sub that allows the server code to react to channel event.
46
+ # Methods for asynchronous execution include {run} (same as {defer}), {run_after} and {run_every}.
70
47
  #
71
- # * {Iodine.subscribe} subscribes the process to a channel (which might be different than a connection's subscription, see {Iodine::Websocket}).
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
- # {Iodine::Websocket} objects have a seperate Pub/Sub implementation that manages the subscription's lifetime to match the connection's lifetime and allows direct client Pub/Sub (forwards the message to the client directly without invoking the Ruby interpreter).
50
+ # Methods for TCP/IP and Unix Sockets connections include {listen} and {connect}.
76
51
  #
77
- # Note that {Iodine.subscribe} returns an {Iodine::PubSub::Subscription} object that can be used for closing the subscription.
52
+ # Methods for HTTP connections include {listen2http}.
78
53
  #
79
- # == Patching Rack
54
+ # Note that the HTTP server supports both TCP/IP and Unix Sockets as well as SSE / WebSockets extensions.
80
55
  #
81
- # Although Iodine offers Rack::Utils optimizations using monkey patching, Iodine does NOT monkey patch Rack automatically.
56
+ # Iodine doesn't call {patch_rack} automatically, but doing so will improve Rack's performace.
82
57
  #
83
- # Choosing to monkey patch Rack::Utils could offer significant performance gains for some applications. i.e. (on my machine):
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
- # Will monkey patch the default JSON parser to replace the default `JSON.parse` with {Iodine::JSON.parse}.
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
- @before_fork_blocks = []
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
- ::Iodine::PubSub.default_engine = ::Iodine::PubSub::CLUSTER
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
@@ -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::SINGLE_PROCESS` will distribute messages to all subscribers sharing the same process.
29
+ # * `Iodine::PubSub::Engine::PROCESS` will distribute messages to all subscribers sharing the same process.
30
30
  #
31
- # {Iodine::PubSub::Engine} instances should be initialized only after Iodine
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::PubSub::Engine#distribute} method.
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
@@ -1,3 +1,3 @@
1
1
  module Iodine
2
- VERSION = '0.5.2'.freeze
2
+ VERSION = '0.6.0'.freeze
3
3
  end
@@ -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
- if @app && @app != app
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.