iodine 0.3.6 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of iodine might be problematic. Click here for more details.

Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +46 -0
  3. data/LIMITS.md +25 -0
  4. data/README.md +39 -80
  5. data/SPEC-Websocket-Draft.md +129 -4
  6. data/bin/echo +2 -2
  7. data/bin/http-hello +1 -0
  8. data/bin/updated api +113 -0
  9. data/bin/ws-echo +0 -1
  10. data/examples/broadcast.ru +56 -0
  11. data/examples/echo.ru +57 -0
  12. data/examples/hello.ru +30 -0
  13. data/examples/redis.ru +69 -0
  14. data/examples/shootout.ru +53 -0
  15. data/exe/iodine +2 -80
  16. data/ext/iodine/defer.c +11 -5
  17. data/ext/iodine/empty.h +26 -0
  18. data/ext/iodine/evio.h +1 -1
  19. data/ext/iodine/facil.c +103 -61
  20. data/ext/iodine/facil.h +20 -12
  21. data/ext/iodine/fio_dict.c +446 -0
  22. data/ext/iodine/fio_dict.h +90 -0
  23. data/ext/iodine/fio_hash_table.h +370 -0
  24. data/ext/iodine/fio_list.h +30 -3
  25. data/ext/iodine/http.c +169 -37
  26. data/ext/iodine/http.h +33 -10
  27. data/ext/iodine/http1.c +78 -42
  28. data/ext/iodine/http_request.c +6 -0
  29. data/ext/iodine/http_request.h +3 -0
  30. data/ext/iodine/http_response.c +43 -11
  31. data/ext/iodine/iodine.c +380 -0
  32. data/ext/iodine/iodine.h +62 -0
  33. data/ext/iodine/iodine_helpers.c +235 -0
  34. data/ext/iodine/iodine_helpers.h +13 -0
  35. data/ext/iodine/iodine_http.c +409 -241
  36. data/ext/iodine/iodine_http.h +7 -14
  37. data/ext/iodine/iodine_protocol.c +626 -0
  38. data/ext/iodine/iodine_protocol.h +13 -0
  39. data/ext/iodine/iodine_pubsub.c +646 -0
  40. data/ext/iodine/iodine_pubsub.h +27 -0
  41. data/ext/iodine/iodine_websockets.c +796 -0
  42. data/ext/iodine/iodine_websockets.h +19 -0
  43. data/ext/iodine/pubsub.c +544 -0
  44. data/ext/iodine/pubsub.h +215 -0
  45. data/ext/iodine/random.c +4 -4
  46. data/ext/iodine/rb-call.c +1 -5
  47. data/ext/iodine/rb-defer.c +3 -20
  48. data/ext/iodine/rb-rack-io.c +22 -22
  49. data/ext/iodine/rb-rack-io.h +3 -4
  50. data/ext/iodine/rb-registry.c +111 -118
  51. data/ext/iodine/redis_connection.c +277 -0
  52. data/ext/iodine/redis_connection.h +77 -0
  53. data/ext/iodine/redis_engine.c +398 -0
  54. data/ext/iodine/redis_engine.h +68 -0
  55. data/ext/iodine/resp.c +842 -0
  56. data/ext/iodine/resp.h +253 -0
  57. data/ext/iodine/sock.c +26 -12
  58. data/ext/iodine/sock.h +14 -3
  59. data/ext/iodine/spnlock.inc +19 -2
  60. data/ext/iodine/websockets.c +299 -11
  61. data/ext/iodine/websockets.h +159 -6
  62. data/lib/iodine.rb +104 -1
  63. data/lib/iodine/cli.rb +106 -0
  64. data/lib/iodine/monkeypatch.rb +40 -0
  65. data/lib/iodine/pubsub.rb +70 -0
  66. data/lib/iodine/version.rb +1 -1
  67. data/lib/iodine/websocket.rb +12 -0
  68. data/lib/rack/handler/iodine.rb +33 -7
  69. metadata +35 -7
  70. data/ext/iodine/iodine_core.c +0 -760
  71. data/ext/iodine/iodine_core.h +0 -79
  72. data/ext/iodine/iodine_websocket.c +0 -551
  73. data/ext/iodine/iodine_websocket.h +0 -22
  74. data/lib/iodine/http.rb +0 -4
@@ -9,6 +9,10 @@ Feel free to copy, use and enjoy according to the license provided.
9
9
 
10
10
  #include "http.h"
11
11
 
12
+ /* *****************************************************************************
13
+ Upgrading from HTTP to Websockets
14
+ ***************************************************************************** */
15
+
12
16
  /**
13
17
  The Websocket type is an opaque type used by the websocket API to provide
14
18
  identify a specific Websocket connection and manage it's internal state.
@@ -109,8 +113,20 @@ ssize_t websocket_upgrade(websocket_settings_s settings);
109
113
  #define websocket_upgrade(...) \
110
114
  websocket_upgrade((websocket_settings_s){__VA_ARGS__})
111
115
 
116
+ /* *****************************************************************************
117
+ Websocket information
118
+ ***************************************************************************** */
119
+
112
120
  /** Returns the opaque user data associated with the websocket. */
113
121
  void *websocket_udata(ws_s *ws);
122
+
123
+ /**
124
+ Sets the opaque user data associated with the websocket.
125
+
126
+ Returns the old value, if any.
127
+ */
128
+ void *websocket_udata_set(ws_s *ws, void *udata);
129
+
114
130
  /**
115
131
  Returns the underlying socket UUID.
116
132
 
@@ -118,20 +134,157 @@ This is only relevant for collecting the protocol object from outside of
118
134
  websocket events, as the socket shouldn't be written to.
119
135
  */
120
136
  intptr_t websocket_uuid(ws_s *ws);
121
- /**
122
- Sets the opaque user data associated with the websocket.
123
137
 
124
- Returns the old value, if any.
138
+ /**
139
+ Counts the number of websocket connections.
125
140
  */
126
- void *websocket_udata_set(ws_s *ws, void *udata);
141
+ size_t websocket_count(void);
142
+
143
+ /* *****************************************************************************
144
+ Websocket Connection Management (write / close)
145
+ ***************************************************************************** */
146
+
127
147
  /** Writes data to the websocket. Returns -1 on failure (0 on success). */
128
148
  int websocket_write(ws_s *ws, void *data, size_t size, uint8_t is_text);
129
149
  /** Closes a websocket connection. */
130
150
  void websocket_close(ws_s *ws);
151
+
152
+ /* *****************************************************************************
153
+ Websocket Pub/Sub
154
+ =================
155
+
156
+ API for websocket pub/sub that can be used to publish messages across process
157
+ boundries.
158
+
159
+ Supports pub/sub engines (see {pubsub.h}) that can connect to a backend service
160
+ such as Redis.
161
+
162
+ The default pub/sub engine (if `NULL` or unspecified) will publish the messages
163
+ to the process cluster (all the processes in `facil_run`).
164
+
165
+ To publish to a channel, use the API provided in {pubsub.h}.
166
+ ***************************************************************************** */
167
+
168
+ /** Pub/sub engine type. Engine documentation is in `pubsub.h` */
169
+ typedef struct pubsub_engine_s pubsub_engine_s;
170
+
171
+ /** Incoming pub/sub messages will be passed along using this data structure. */
172
+ typedef struct {
173
+ /** the websocket receiving the message. */
174
+ ws_s *ws;
175
+ /** the pub/sub engine from which where the message originated. */
176
+ struct pubsub_engine_s *engine;
177
+ /** the Websocket pub/sub subscription ID. */
178
+ uintptr_t subscription_id;
179
+ /** the channel where the message was published. */
180
+ struct {
181
+ const char *name;
182
+ size_t len;
183
+ } channel;
184
+ /** the published message. */
185
+ struct {
186
+ const char *data;
187
+ size_t len;
188
+ } msg;
189
+ /** Pattern matching was used for channel subscription. */
190
+ unsigned use_pattern : 1;
191
+ /** user opaque data. */
192
+ void *udata;
193
+ } websocket_pubsub_notification_s;
194
+
195
+ /** Possible arguments for the {websocket_subscribe} function. */
196
+ struct websocket_subscribe_s {
197
+ /** the websocket receiving the message. REQUIRED. */
198
+ ws_s *ws;
199
+ /**
200
+ * The pub/sub engine to use.
201
+ *
202
+ * Default: engine will publish messages throughout the facil process cluster.
203
+ */
204
+ struct pubsub_engine_s *engine;
205
+ /** the channel where the message was published. */
206
+ struct {
207
+ const char *name;
208
+ size_t len;
209
+ } channel;
210
+ /**
211
+ * The callback that handles pub/sub notifications.
212
+ *
213
+ * Default: send directly to websocket client.
214
+ */
215
+ void (*on_message)(websocket_pubsub_notification_s notification);
216
+ /**
217
+ * An optional cleanup callback for the `udata`.
218
+ */
219
+ void (*on_unsubscribe)(void *udata);
220
+ /** User opaque data, passed along to the notification. */
221
+ void *udata;
222
+ /** Use pattern matching for channel subscription. */
223
+ unsigned use_pattern : 1;
224
+ /**
225
+ * When using client forwarding (no `on_message` callback), this indicates if
226
+ * messages should be sent to the client as binary blobs, which is the safest
227
+ * approach.
228
+ *
229
+ * Default: tests for UTF-8 data encoding and sends as text if valid UTF-8.
230
+ * Messages above ~32Kb are always assumed to be binary.
231
+ */
232
+ unsigned force_binary : 1;
233
+ /**
234
+ * When using client forwarding (no `on_message` callback), this indicates if
235
+ * messages should be sent to the client as text.
236
+ *
237
+ * `force_binary` has precedence.
238
+ *
239
+ * Default: see above.
240
+ *
241
+ */
242
+ unsigned force_text : 1;
243
+ };
244
+
131
245
  /**
132
- Counts the number of websocket connections.
246
+ * Subscribes to a channel. See {struct websocket_subscribe_s} for possible
247
+ * arguments.
248
+ *
249
+ * Returns a subscription ID on success and 0 on failure.
250
+ *
251
+ * All subscriptions are automatically revoked once the websocket is closed.
252
+ *
253
+ * If the connections subscribes to the same channel more than once, messages
254
+ * will be merged. However, another subscription ID will be assigned, since two
255
+ * calls to {websocket_unsubscribe} will be required in order to unregister from
256
+ * the channel.
257
+ */
258
+ uintptr_t websocket_subscribe(struct websocket_subscribe_s args);
259
+
260
+ #define websocket_subscribe(wbsckt, ...) \
261
+ websocket_subscribe((struct websocket_subscribe_s){.ws = wbsckt, __VA_ARGS__})
262
+
263
+ /**
264
+ * Finds an existing subscription (in case the subscription ID wasn't saved).
265
+ * See {struct websocket_subscribe_s} for possible arguments.
266
+ *
267
+ * Returns the existing subscription's ID (if exists) or 0 (no subscription).
268
+ */
269
+ uintptr_t websocket_find_sub(struct websocket_subscribe_s args);
270
+
271
+ #define websocket_find_sub(wbsckt, ...) \
272
+ websocket_find_sub((struct websocket_subscribe_s){.ws = wbsckt, __VA_ARGS__})
273
+
274
+ /**
275
+ * Unsubscribes from a channel.
276
+ *
277
+ * Failures are silent.
278
+ *
279
+ * All subscriptions are automatically revoked once the websocket is closed. So
280
+ * only use this function to unsubscribe while the websocket is open.
281
+ */
282
+ void websocket_unsubscribe(ws_s *ws, uintptr_t subscription_id);
283
+
284
+ /* *****************************************************************************
285
+ Websocket Tasks - within a single process scope, NOT and entire cluster
286
+ *****************************************************************************
133
287
  */
134
- size_t websocket_count(void);
135
288
 
136
289
  /** The named arguments for `websocket_each` */
137
290
  struct websocket_each_args_s {
@@ -26,6 +26,92 @@ require 'iodine/iodine'
26
26
  #
27
27
  #
28
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 and HTTP Rack applications.
61
+ # * {Iodine.count} counts the number of connections (including HTTP / Websocket connections).
62
+ # * {Iodine::Protocol.each} runs a code of block for every connection sharing the process (except HTTP / Websocket connections).
63
+ # * {Iodine::Websocket.each} runs a code of block for every existing websocket sharing the process.
64
+ #
65
+ # 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.
66
+ #
67
+ # === Pub/Sub
68
+ #
69
+ # Iodine offers a native Pub/Sub engine (no database required) that can be easily extended by implementing a Pub/Sub {Iodine::PubSub::Engine}.
70
+ #
71
+ # The following methods offect server side Pub/Sub that allows the server code to react to channel event.
72
+ #
73
+ # * {Iodine.subscribe}, {Iodine.unsubscribe} manages a process's subscription to a channel (which is different than a connection's subscription, such as employed by {Iodine::Websocket}).
74
+ # * {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 even other machines (when using the {Iodine::PubSub::RedisEngine}).
75
+ # * {Iodine.default_pubsub=}, {Iodine.default_pubsub} 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).
76
+ #
77
+ # {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).
78
+ #
79
+ # == Patching Rack
80
+ #
81
+ # Although Iodine offers Rack::Utils optimizations using monkey patching, Iodine does NOT monkey patch Rack automatically.
82
+ #
83
+ # Choosing to monkey patch Rack::Utils could offer significant performance gains for some applications. i.e. (on my machine):
84
+ #
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.
29
115
  module Iodine
30
116
  @threads = (ARGV.index('-t') && ARGV[ARGV.index('-t') + 1]) || ENV['MAX_THREADS']
31
117
  @processes = (ARGV.index('-w') && ARGV[ARGV.index('-w') + 1]) || ENV['MAX_WORKERS']
@@ -64,7 +150,7 @@ module Iodine
64
150
  # file to load now instead of waiting for "first access". This allows multi-threaded safety and better memory utilization during forking.
65
151
  #
66
152
  # Use {warmup} when either {processes} or {threads} are set to more then 1.
67
- def self.warmup
153
+ def self.warmup app
68
154
  # load anything marked with `autoload`, since autoload isn't thread safe nor fork friendly.
69
155
  Module.constants.each do |n|
70
156
  begin
@@ -72,7 +158,24 @@ module Iodine
72
158
  rescue Exception => _e
73
159
  end
74
160
  end
161
+ ::Rack::Builder.new(app) do |r|
162
+ r.warmup do |a|
163
+ client = ::Rack::MockRequest.new(a)
164
+ client.get('/')
165
+ end
166
+ end
75
167
  end
168
+
169
+ def self.patch_rack
170
+ ::Rack::Utils.class_eval do
171
+ Iodine::Base::MonkeyPatch::RackUtils.methods(false).each do |m|
172
+ ::Rack::Utils.define_singleton_method(m,
173
+ Iodine::Base::MonkeyPatch::RackUtils.instance_method(m) )
174
+ end
175
+ end
176
+ end
177
+
178
+ self.default_pubsub = ::Iodine::PubSub::CLUSTER
76
179
  end
77
180
 
78
181
  require 'rack/handler/iodine' unless defined? ::Iodine::Rack::IODINE_RACK_LOADED
@@ -0,0 +1,106 @@
1
+ require 'iodine'
2
+ require 'rack'
3
+
4
+ module Iodine
5
+ module Base
6
+ # Command line interface
7
+ module CLI
8
+
9
+ def print_help
10
+ puts <<-EOS
11
+
12
+ Iodine's HTTP/Websocket server version #{Iodine::VERSION}
13
+
14
+ Use:
15
+
16
+ iodine <options> <filename>
17
+
18
+ Both <options> and <filename> are optional.
19
+
20
+ Available options:
21
+ -p Port number. Default: 3000.
22
+ -t Number of threads. Default: CPU core count.
23
+ -w Number of worker processes. Default: CPU core count.
24
+ -www Public folder for static file serving. Default: nil (none).
25
+ -v Log responses. Default: never log responses.
26
+ -warmup Warmup invokes autoloading (lazy loading) during server startup.
27
+ -tout HTTP inactivity connection timeout. Default: 5 seconds.
28
+ -maxbd Maximum Mb per HTTP message (max body size). Default: 50Mib.
29
+ -maxms Maximum Bytes per Websocket message. Default: 250Kib.
30
+ -ping Websocket ping interval in seconds. Default: 40 seconds.
31
+ <filename> Defaults to: config.ru
32
+
33
+ Example:
34
+
35
+ iodine -p 80
36
+
37
+ iodine -p 8080 path/to/app/conf.ru
38
+
39
+ iodine -p 8080 -w 4 -t 16
40
+
41
+ EOS
42
+ end
43
+
44
+
45
+ def try_file filename
46
+ return nil unless File.exist? filename
47
+ return ::Rack::Builder.parse_file filename
48
+ end
49
+
50
+
51
+
52
+ def call
53
+ if ARGV[0] =~ /(\-\?)|(help)|(\?)|(h)|(\-h)$/
54
+ return print_help
55
+ end
56
+
57
+ app, opt = nil, nil
58
+ filename = ((ARGV[-2].to_s[0] != '-' || ARGV[-2].to_s == '-warmup') && ARGV[-1].to_s[0] != '-' && ARGV[-1])
59
+ if filename
60
+ app, opt = try_file filename;
61
+ unless opt
62
+ puts "* Couldn't find #{filename}\n testing for config.ru\n"
63
+ app, opt = try_file "config.ru"
64
+ end
65
+ else
66
+ app, opt = try_file "config.ru";
67
+ end
68
+
69
+ unless opt
70
+ puts "WARNING: Ruby application not found#{ filename ? " - missing both #{filename} and config.ru" : " - missing config.ru"}."
71
+ if ARGV.index('-www') && ARGV[ARGV.index('-www') + 1]
72
+ puts " Running only static file service."
73
+ opt = ::Rack::Server::Options.new.parse!([])
74
+ else
75
+ puts "For help run:"
76
+ puts " iodine -?"
77
+ return
78
+ end
79
+ end
80
+
81
+ if ARGV.index('-maxbd') && ARGV[ARGV.index('-maxbd') + 1]
82
+ Iodine::Rack.max_body_size = ARGV[ARGV.index('-maxbd') + 1].to_i
83
+ end
84
+ if ARGV.index('-maxms') && ARGV[ARGV.index('-maxms') + 1]
85
+ Iodine::Rack.max_msg_size = ARGV[ARGV.index('-maxms') + 1].to_i
86
+ end
87
+ if ARGV.index('-ping') && ARGV[ARGV.index('-ping') + 1]
88
+ Iodine::Rack.ws_timeout = ARGV[ARGV.index('-ping') + 1].to_i
89
+ end
90
+ if ARGV.index('-www') && ARGV[ARGV.index('-www') + 1]
91
+ Iodine::Rack.public = ARGV[ARGV.index('-www') + 1]
92
+ end
93
+ if ARGV.index('-tout') && ARGV[ARGV.index('-tout') + 1]
94
+ Iodine::Rack.timeout = ARGV[ARGV.index('-tout') + 1].to_i
95
+ puts "WARNNING: Iodine::Rack.timeout set to 0 (ignored, timeout will be ~5 seconds)."
96
+ end
97
+ Iodine::Rack.log = true if ARGV.index('-v')
98
+ Iodine::Rack.log = false if ARGV.index('-q')
99
+ Iodine.warmup(app) if ARGV.index('-warmup')
100
+ Iodine::Rack.run(app, opt)
101
+ end
102
+
103
+ extend self
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,40 @@
1
+ module Iodine
2
+ module Base
3
+ # Iodine does NOT monkey patch automatically at this time.
4
+ #
5
+ # This may change in future releases, but that's unlikely.
6
+ module MonkeyPatch
7
+ # Iodine does NOT monkey patch Rack automatically. However, it's possible to
8
+ # moneky patch Rack::Utils using this module.
9
+ #
10
+ # Choosing to monkey patch Rack::Utils could offer significant performance gains for some applications. i.e. (on my machine):
11
+ #
12
+ # require 'iodine'
13
+ # require 'rack'
14
+ # s = '%E3%83%AB%E3%83%93%E3%82%A4%E3%82%B9%E3%81%A8'
15
+ # Benchmark.bm do |bm|
16
+ # # Pre-Patch
17
+ # bm.report("Rack") {1_000_000.times { Rack::Utils.unescape s } }
18
+ #
19
+ # # Perform Patch
20
+ # Rack::Utils.class_eval do
21
+ # Iodine::Base::MonkeyPatch::RackUtils.methods(false).each do |m|
22
+ # define_singleton_method(m,
23
+ # Iodine::Base::MonkeyPatch::RackUtils.instance_method(m) )
24
+ # end
25
+ # end
26
+ #
27
+ # # Post Patch
28
+ # bm.report("Patched") {1_000_000.times { Rack::Utils.unescape s } }
29
+ # end && nil
30
+ #
31
+ # Results:
32
+ # user system total real
33
+ # Rack 8.620000 0.020000 8.640000 ( 8.636676)
34
+ # Patched 0.320000 0.000000 0.320000 ( 0.322377)
35
+ module RackUtils
36
+ end
37
+ end
38
+ end
39
+ end
40
+ \