iodine 0.7.59 → 0.8.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.
Files changed (160) hide show
  1. checksums.yaml +4 -4
  2. data/.clang-format +15 -0
  3. data/.rspec +1 -0
  4. data/.yardopts +1 -3
  5. data/CHANGELOG.md +132 -12
  6. data/Gemfile +5 -7
  7. data/LICENSE.txt +1 -1
  8. data/README.md +147 -671
  9. data/Rakefile +9 -16
  10. data/examples/chatroom.nru +9 -0
  11. data/examples/client +74 -0
  12. data/examples/config-hello.nru +9 -0
  13. data/examples/config-hello.ru +11 -0
  14. data/examples/config-hijack.ru +13 -0
  15. data/examples/config-shootout.ru +43 -0
  16. data/examples/config.nru +120 -0
  17. data/examples/config.rb +37 -0
  18. data/examples/config.ru +42 -53
  19. data/examples/fake +24 -0
  20. data/examples/hello_neorack +137 -0
  21. data/examples/routes.nru +29 -0
  22. data/examples/routes.ru +42 -0
  23. data/exe/iodine +218 -258
  24. data/ext/iodine/extconf.rb +27 -100
  25. data/ext/iodine/fio-stl.h +128336 -0
  26. data/ext/iodine/fio-stl.md +26396 -0
  27. data/ext/iodine/iodine.c +213 -1393
  28. data/ext/iodine/iodine.h +250 -55
  29. data/ext/iodine/iodine_arg_helper.h +329 -0
  30. data/ext/iodine/iodine_caller.h +234 -25
  31. data/ext/iodine/iodine_cli.h +434 -0
  32. data/ext/iodine/iodine_compression.h +312 -0
  33. data/ext/iodine/iodine_connection.h +3484 -34
  34. data/ext/iodine/iodine_core.h +363 -0
  35. data/ext/iodine/iodine_crypto.h +1441 -0
  36. data/ext/iodine/iodine_defer.h +225 -4
  37. data/ext/iodine/iodine_json.h +738 -4
  38. data/ext/iodine/iodine_listener.h +263 -0
  39. data/ext/iodine/iodine_log.h +297 -0
  40. data/ext/iodine/iodine_minimap.h +543 -0
  41. data/ext/iodine/iodine_mustache.h +461 -4
  42. data/ext/iodine/iodine_pubsub_eng.h +729 -0
  43. data/ext/iodine/iodine_pubsub_history.h +547 -0
  44. data/ext/iodine/iodine_pubsub_msg.h +228 -0
  45. data/ext/iodine/iodine_redis.h +322 -0
  46. data/ext/iodine/iodine_store.h +418 -16
  47. data/ext/iodine/iodine_threads.h +279 -0
  48. data/ext/iodine/iodine_tls.h +515 -6
  49. data/ext/iodine/iodine_utils.h +923 -0
  50. data/iodine.gemspec +37 -42
  51. data/lib/iodine/benchmark.rb +1690 -0
  52. data/lib/iodine/documentation.rb +3085 -0
  53. data/lib/iodine/version.rb +17 -1
  54. data/lib/iodine.rb +54 -255
  55. data/logo.svg +36 -0
  56. data/mustache.md +55 -0
  57. data/sig/iodine.rbs +4 -0
  58. metadata +58 -184
  59. data/.github/ISSUE_TEMPLATE/bug_report.md +0 -40
  60. data/.github/workflows/ruby.yml +0 -42
  61. data/.gitignore +0 -20
  62. data/LIMITS.md +0 -41
  63. data/SPEC-PubSub-Draft.md +0 -159
  64. data/SPEC-WebSocket-Draft.md +0 -239
  65. data/bin/console +0 -22
  66. data/bin/info.md +0 -353
  67. data/bin/mustache_bench.rb +0 -100
  68. data/bin/poc/Gemfile.lock +0 -23
  69. data/bin/poc/README.md +0 -37
  70. data/bin/poc/config.ru +0 -66
  71. data/bin/poc/gemfile +0 -1
  72. data/bin/poc/www/index.html +0 -57
  73. data/examples/async_task.ru +0 -92
  74. data/examples/bates/README.md +0 -3
  75. data/examples/bates/config.ru +0 -342
  76. data/examples/bates/david+bold.pdf +0 -0
  77. data/examples/bates/public/drop-pdf.png +0 -0
  78. data/examples/bates/public/index.html +0 -600
  79. data/examples/echo.ru +0 -59
  80. data/examples/etag.ru +0 -16
  81. data/examples/hello.ru +0 -29
  82. data/examples/pubsub_engine.ru +0 -81
  83. data/examples/rack3.ru +0 -12
  84. data/examples/redis.ru +0 -70
  85. data/examples/shootout.ru +0 -73
  86. data/examples/sub-protocols.ru +0 -90
  87. data/examples/tcp_client.rb +0 -66
  88. data/examples/x-sendfile.ru +0 -14
  89. data/ext/iodine/fio.c +0 -11995
  90. data/ext/iodine/fio.h +0 -6371
  91. data/ext/iodine/fio_cli.c +0 -431
  92. data/ext/iodine/fio_cli.h +0 -189
  93. data/ext/iodine/fio_json_parser.h +0 -688
  94. data/ext/iodine/fio_siphash.c +0 -157
  95. data/ext/iodine/fio_siphash.h +0 -37
  96. data/ext/iodine/fio_tls.h +0 -129
  97. data/ext/iodine/fio_tls_missing.c +0 -649
  98. data/ext/iodine/fio_tls_openssl.c +0 -1106
  99. data/ext/iodine/fio_tmpfile.h +0 -50
  100. data/ext/iodine/fiobj.h +0 -44
  101. data/ext/iodine/fiobj4fio.h +0 -21
  102. data/ext/iodine/fiobj_ary.c +0 -333
  103. data/ext/iodine/fiobj_ary.h +0 -139
  104. data/ext/iodine/fiobj_data.c +0 -1187
  105. data/ext/iodine/fiobj_data.h +0 -167
  106. data/ext/iodine/fiobj_hash.c +0 -409
  107. data/ext/iodine/fiobj_hash.h +0 -176
  108. data/ext/iodine/fiobj_json.c +0 -622
  109. data/ext/iodine/fiobj_json.h +0 -68
  110. data/ext/iodine/fiobj_mem.h +0 -71
  111. data/ext/iodine/fiobj_mustache.c +0 -317
  112. data/ext/iodine/fiobj_mustache.h +0 -62
  113. data/ext/iodine/fiobj_numbers.c +0 -344
  114. data/ext/iodine/fiobj_numbers.h +0 -127
  115. data/ext/iodine/fiobj_str.c +0 -433
  116. data/ext/iodine/fiobj_str.h +0 -172
  117. data/ext/iodine/fiobject.c +0 -620
  118. data/ext/iodine/fiobject.h +0 -655
  119. data/ext/iodine/hpack.h +0 -1923
  120. data/ext/iodine/http.c +0 -2743
  121. data/ext/iodine/http.h +0 -1002
  122. data/ext/iodine/http1.c +0 -910
  123. data/ext/iodine/http1.h +0 -29
  124. data/ext/iodine/http1_parser.h +0 -878
  125. data/ext/iodine/http_internal.c +0 -1279
  126. data/ext/iodine/http_internal.h +0 -237
  127. data/ext/iodine/http_mime_parser.h +0 -350
  128. data/ext/iodine/iodine_caller.c +0 -230
  129. data/ext/iodine/iodine_connection.c +0 -941
  130. data/ext/iodine/iodine_defer.c +0 -420
  131. data/ext/iodine/iodine_fiobj2rb.h +0 -120
  132. data/ext/iodine/iodine_helpers.c +0 -282
  133. data/ext/iodine/iodine_helpers.h +0 -12
  134. data/ext/iodine/iodine_http.c +0 -1181
  135. data/ext/iodine/iodine_http.h +0 -23
  136. data/ext/iodine/iodine_json.c +0 -302
  137. data/ext/iodine/iodine_mustache.c +0 -567
  138. data/ext/iodine/iodine_pubsub.c +0 -580
  139. data/ext/iodine/iodine_pubsub.h +0 -26
  140. data/ext/iodine/iodine_rack_io.c +0 -273
  141. data/ext/iodine/iodine_rack_io.h +0 -20
  142. data/ext/iodine/iodine_store.c +0 -146
  143. data/ext/iodine/iodine_tcp.c +0 -346
  144. data/ext/iodine/iodine_tcp.h +0 -13
  145. data/ext/iodine/iodine_tls.c +0 -261
  146. data/ext/iodine/mustache_parser.h +0 -1546
  147. data/ext/iodine/redis_engine.c +0 -957
  148. data/ext/iodine/redis_engine.h +0 -79
  149. data/ext/iodine/resp_parser.h +0 -317
  150. data/ext/iodine/websocket_parser.h +0 -506
  151. data/ext/iodine/websockets.c +0 -743
  152. data/ext/iodine/websockets.h +0 -185
  153. data/lib/iodine/connection.rb +0 -61
  154. data/lib/iodine/json.rb +0 -42
  155. data/lib/iodine/mustache.rb +0 -113
  156. data/lib/iodine/pubsub.rb +0 -55
  157. data/lib/iodine/rack_utils.rb +0 -43
  158. data/lib/iodine/tls.rb +0 -16
  159. data/lib/rack/handler/iodine.rb +0 -33
  160. data/logo.png +0 -0
@@ -0,0 +1,42 @@
1
+ # This is a multi-route example using NeoRack vs Rack `map`
2
+
3
+ class RouteApp
4
+ def initialize(name)
5
+ @response = [200, {}, "Hello #{name}"]
6
+ end
7
+ def call(e)
8
+ @response
9
+ end
10
+ end
11
+
12
+ ROUTE_COUNT = 100
13
+ BSET_CASE_FOR_TRI = true
14
+
15
+ if BSET_CASE_FOR_TRI # best case
16
+ ROUTE_COUNT.times {|i| url = "/try_this_route-#{i}" ; map(url) { run RouteApp.new(url) } }
17
+ puts "\t test with:"
18
+ puts "\t wrk -c200 -d4 -t2 http://localhost:3000/try_this_route-0"
19
+ puts "\t wrk -c200 -d4 -t2 http://localhost:3000/try_this_route-#{ROUTE_COUNT / 2}"
20
+ puts "\t wrk -c200 -d4 -t2 http://localhost:3000/try_this_route-#{ROUTE_COUNT - 1}"
21
+ else
22
+ ROUTE_COUNT.times {|i| url = "/#{i}-try_this_route" ; map(url) { run RouteApp.new(url) } }
23
+ puts "\t test with:"
24
+ puts "\t wrk -c200 -d4 -t2 http://localhost:3000/0-try_this_route"
25
+ puts "\t wrk -c200 -d4 -t2 http://localhost:3000/#{ROUTE_COUNT / 2}-try_this_route"
26
+ puts "\t wrk -c200 -d4 -t2 http://localhost:3000/#{ROUTE_COUNT - 1}-try_this_route"
27
+ end
28
+
29
+ run {[404, {}, nil]}
30
+
31
+
32
+ #### Rails Approach
33
+ # GET / => index
34
+ # GET /new => new
35
+ # POST / => create
36
+ # GET /:id => show
37
+ # GET /:id/edit => edit
38
+ # PATCH/PUT /:id => update
39
+ # DELETE /:id => destroy
40
+ # Historical?
41
+ # POST /:id => update
42
+
data/exe/iodine CHANGED
@@ -1,280 +1,240 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
- IODINE_PARSE_CLI = true
4
3
  require 'iodine'
4
+ require 'pathname'
5
5
 
6
- # Load Rack if available (assume it will be used).
7
- #
8
- # Remember, code costs memory. Duplicating the Rack::Builder module and functions can be avoided.
9
- begin
10
- require 'rack'
11
- module Iodine
12
- module Base
13
- module Rack
14
- Builder = ::Rack::Builder
6
+ module Iodine
7
+ class Base
8
+ class NeoRackUp
9
+ module NOOPHandler
15
10
  end
16
- end
17
- end
18
- rescue LoadError
19
- puts "WARNING: The gam `rack` wasn't loaded before iodine started loading the rack app, using custom loader."
20
- module Iodine
21
- module Base
22
- module Rack
23
- # The Rack::Builder code is used when Rack isn't available.
24
- #
25
- # The code was copied (with minor adjustments) from the Rack source code and is licensed under the MIT license.
26
- # Copyright (C) 2007-2019 Leah Neukirchen <http://leahneukirchen.org/infopage.html>
27
- #
28
- # ====
29
- #
30
- # Rack::Builder implements a small DSL to iteratively construct Rack
31
- # applications.
32
- #
33
- # Example:
34
- #
35
- # require 'rack/lobster'
36
- # app = Rack::Builder.new do
37
- # use Rack::CommonLogger
38
- # use Rack::ShowExceptions
39
- # map "/lobster" do
40
- # use Rack::Lint
41
- # run Rack::Lobster.new
42
- # end
43
- # end
44
- #
45
- # run app
46
- #
47
- # Or
48
- #
49
- # app = Rack::Builder.app do
50
- # use Rack::CommonLogger
51
- # run lambda { |env| [200, {'Content-Type' => 'text/plain'}, ['OK']] }
52
- # end
53
- #
54
- # run app
55
- #
56
- # +use+ adds middleware to the stack, +run+ dispatches to an application.
57
- # You can use +map+ to construct a Rack::URLMap in a convenient way.
58
- class Builder
59
- # https://stackoverflow.com/questions/2223882/whats-the-difference-between-utf-8-and-utf-8-without-bom
60
- UTF_8_BOM = '\xef\xbb\xbf'
61
-
62
- def self.parse_file(config, opts = Hash.new)
63
- if config =~ /\.ru$/
64
- return self.load_file(config, opts)
65
- else
66
- require config
67
- app = Object.const_get(::File.basename(config, '.rb').split('_').map(&:capitalize).join(''))
68
- return app, {}
69
- end
70
- end
71
-
72
- def self.load_file(path, options = Hash.new)
73
- cfgfile = ::File.read(path)
74
- cfgfile.slice!(/\A#{UTF_8_BOM}/) if cfgfile.encoding == Encoding::UTF_8
75
-
76
- cfgfile.sub!(/^__END__\n.*\Z/m, '')
77
- app = new_from_string cfgfile, path
78
-
79
- return app, options
80
- end
81
-
82
- def self.new_from_string(builder_script, file = "(rackup)")
83
- eval "Iodine::Base::Rack::Builder.new {\n" + builder_script + "\n}.to_app",
84
- TOPLEVEL_BINDING, file, 0
85
- end
86
-
87
- def initialize(default_app = nil, &block)
88
- @use, @map, @run, @warmup, @freeze_app = [], nil, default_app, nil, false
89
- instance_eval(&block) if block_given?
90
- end
91
-
92
- def self.app(default_app = nil, &block)
93
- self.new(default_app, &block).to_app
94
- end
95
-
96
- # Specifies middleware to use in a stack.
97
- #
98
- # class Middleware
99
- # def initialize(app)
100
- # @app = app
101
- # end
102
- #
103
- # def call(env)
104
- # env["rack.some_header"] = "setting an example"
105
- # @app.call(env)
106
- # end
107
- # end
108
- #
109
- # use Middleware
110
- # run lambda { |env| [200, { "Content-Type" => "text/plain" }, ["OK"]] }
111
- #
112
- # All requests through to this application will first be processed by the middleware class.
113
- # The +call+ method in this example sets an additional environment key which then can be
114
- # referenced in the application if required.
115
- def use(middleware, *args, &block)
116
- if @map
117
- mapping, @map = @map, nil
118
- @use << proc { |app| generate_map(app, mapping) }
119
- end
120
- @use << proc { |app| middleware.new(app, *args, &block) }
121
- end
122
-
123
- # Takes an argument that is an object that responds to #call and returns a Rack response.
124
- # The simplest form of this is a lambda object:
125
- #
126
- # run lambda { |env| [200, { "Content-Type" => "text/plain" }, ["OK"]] }
127
- #
128
- # However this could also be a class:
129
- #
130
- # class Heartbeat
131
- # def self.call(env)
132
- # [200, { "Content-Type" => "text/plain" }, ["OK"]]
133
- # end
134
- # end
135
- #
136
- # run Heartbeat
137
- def run(app = nil, &block)
138
- raise ArgumentError, "Both app and block given!" if app && block_given?
139
- @run = app || block
140
- end
141
-
142
- # Takes a lambda or block that is used to warm-up the application.
143
- #
144
- # warmup do |app|
145
- # client = Rack::MockRequest.new(app)
146
- # client.get('/')
147
- # end
148
- #
149
- # use SomeMiddleware
150
- # run MyApp
151
- def warmup(prc = nil, &block)
152
- @warmup = prc || block
153
- end
154
-
155
- # Creates a route within the application.
156
- #
157
- # Rack::Builder.app do
158
- # map '/' do
159
- # run Heartbeat
160
- # end
161
- # end
162
- #
163
- # The +use+ method can also be used here to specify middleware to run under a specific path:
164
- #
165
- # Rack::Builder.app do
166
- # map '/' do
167
- # use Middleware
168
- # run Heartbeat
169
- # end
170
- # end
171
- #
172
- # This example includes a piece of middleware which will run before requests hit +Heartbeat+.
173
- #
174
- def map(path, &block)
175
- @map ||= {}
176
- @map[path] = block
177
- end
178
-
179
- # Freeze the app (set using run) and all middleware instances when building the application
180
- # in to_app.
181
- def freeze_app
182
- @freeze_app = true
183
- end
184
-
185
- def to_app
186
- app = @map ? generate_map(@run, @map) : @run
187
- fail "missing run or map statement" unless app
188
- app.freeze if @freeze_app
189
- app = @use.reverse.inject(app) { |a, e| e[a].tap { |x| x.freeze if @freeze_app } }
190
- @warmup.call(app) if @warmup
191
- app
192
- end
193
-
194
- def call(env)
195
- to_app.call(env)
196
- end
197
-
198
- private
199
-
200
- def generate_map(default_app, mapping)
201
- mapped = default_app ? { '/' => default_app } : {}
202
- mapping.each { |r, b| mapped[r] = self.class.new(default_app, &b).to_app }
203
- URLMap.new(mapped)
204
- end
11
+ module MiddleWarePre__
12
+ def initialize(*args, &blk); @app = args[0]; super; end
13
+ private
14
+ def method_missing(method_name, *arguments)
15
+ m = self.define_singleton_method(method_name) {|*args| @app.send(method_name, *args) }
16
+ m.call(*arguments)
205
17
  end
206
18
  end
207
- end
208
- end
209
- end
19
+ module MiddleWareDef__
20
+ def on_http(e); @app.on_http(e); end
21
+ def on_finish(e); @app.on_finish(e); end
22
+ def call(env); @app.call(env); end
23
+ def on_authenticate(e); @app.on_authenticate(e); end
24
+ def on_authenticate_sse(e); @app.on_authenticate_sse(e); end
25
+ def on_authenticate_websocket(e); @app.on_authenticate_websocket(e); end
26
+ def on_open(e); @app.on_open(e); end
27
+ def on_message(e, data); @app.on_message(e, data); end
28
+ def on_drained(e); @app.on_drained(e); end
29
+ def on_timeout(e); @app.on_timeout(e); end
30
+ def on_shutdown(e); @app.on_shutdown(e); end
31
+ def on_close(e); @app.on_close(e); end
32
+ def on_eventsource(e, message); @app.on_eventsource(e, message); end
33
+ def on_eventsource_reconnect(e,id); @app.on_eventsource_reconnect(e,id); end
34
+ end
210
35
 
211
- module Iodine
212
- # The Iodine::Base namespace is reserved for internal use and is NOT part of the public API.
213
- module Base
214
- # Command line interface. The Ruby CLI might be changed in future versions.
215
- module CLI
36
+ UTF_8_BOM = "\xef\xbb\xbf"
216
37
 
217
- def self.try_file filename
218
- return nil unless File.exist? filename
219
- result = Iodine::Base::Rack::Builder.parse_file filename
220
- return result[0] if(result.is_a?(Array))
221
- return result
38
+ # uses specified Middleware class.
39
+ def use(mw, *args, &block)
40
+ @middleware ||= []
41
+ mw.prepend ::Iodine::Base::NeoRackUp::MiddleWarePre__
42
+ mw.include ::Iodine::Base::NeoRackUp::MiddleWareDef__
43
+ @middleware <<[mw, args, block]
222
44
  end
223
-
224
- def self.get_app_opts
225
- app, opt = nil, Hash.new
226
- filename = Iodine::DEFAULT_SETTINGS[:filename_]
227
- if filename
228
- app = try_file(filename)
229
- app = try_file("#{filename}.ru") unless app
230
- unless app
231
- puts "* Couldn't find #{filename}\n testing for config.ru\n"
232
- app = try_file "config.ru"
233
- end
234
- else
235
- app = try_file "config.ru";
45
+ # Maps a URL path to a specific NeoRack Application
46
+ #
47
+ # Accepts an optional block for nested URL mapping and middleware use.
48
+ def map(path, handler = nil, &block)
49
+ path = "/" if path.nil?
50
+ raise "`path` must be a String in a call to map(path)." unless path.is_a?(String)
51
+ @middleware ||= []
52
+ @app ||= nil
53
+ @map ||= {}
54
+ @path_prefix ||= "".dup
55
+ old_middleware = @middleware.dup
56
+ old_app = @app
57
+ old_path_prefix = @path_prefix.dup
58
+ # prevent excess '/' and ignore special '*' allowances
59
+ @path_prefix.slice!(0, @path_prefix.length - 2) if (@path_prefix[-1] == '*' && @path_prefix[-2] == '/')
60
+ @path_prefix.slice!(0, @path_prefix.length - 1) if (@path_prefix[-1] == '/')
61
+ @path_prefix << '/' unless (path[0] == '/')
62
+ @path_prefix << path
63
+ @app = handler
64
+ block.call if block
65
+ puts "WARNING: overwriting existing route mapping @ #{@path_prefix}" if(@map[@path_prefix])
66
+ @map[@path_prefix] = @app
67
+ @middleware = old_middleware
68
+ @app = old_app
69
+ @path_prefix = old_path_prefix
70
+ rescue Exception => e
71
+ err = e
72
+ @app = nil
73
+ Iodine.stop
74
+ raise e
75
+ end
76
+ # Makes the calling module into a NeoRack Resource handler.
77
+ def neorack_resource(*ha)
78
+ ha.each {|handler| Server.make_resource(handler) }
79
+ end
80
+ # Sets NeoRack application for the server to run.
81
+ def run(a = nil, &block)
82
+ a = block if(!a)
83
+ raise "`run` called more than once." if @app
84
+ raise "Missing either an application handler or a block for the `run` instruction." unless a
85
+ raise "Missing both `on_http(e)` and `call(env)` in provided App." unless (a.respond_to?(:on_http) || a.respond_to?(:call))
86
+ Iodine::Base.add_missing_handler_methods(a)
87
+ while(@middleware && !@middleware.empty?)
88
+ m = @middleware.pop
89
+ a = m[0].new(a, *m[1], &m[2])
236
90
  end
237
-
238
- unless app
239
- puts "WARNING: Ruby application not found#{ filename ? " - missing both #{filename} and config.ru" : " - missing config.ru"}."
240
- if Iodine::DEFAULT_SETTINGS[:public]
241
- puts " Running only static file service."
242
- app = Proc.new { [404, {}, "Not Found!"] }
243
- else
244
- puts "\nERROR: Couldn't run Ruby application, check command line arguments."
245
- ARGV << "-?"
246
- Iodine::Base::CLI.parse
247
- exit(0);
248
- end
91
+ @app = a
92
+ rescue Exception => e
93
+ err = e
94
+ @app = nil
95
+ Iodine.stop
96
+ raise e
97
+ end
98
+ # Returns either the NeoRack Application or the routing map (if using a map) to be used.
99
+ def app
100
+ @map ||= {}
101
+ @map['/'] ||= @app
102
+ @map
103
+ end
104
+ # validate name of Web Application configuration file.
105
+ def self.iodine___detect_filename
106
+ file_name = Iodine::Base::CLI.parse(true)[0];
107
+ if(!file_name || File.directory?(file_name))
108
+ file_name ||= './'
109
+ file_name += '/' unless file_name.end_with?('/')
110
+ defaults = ["#{file_name}config.ru", "#{file_name}config.nru"]
111
+ defaults.pop if Iodine::Base::CLI['-R']
112
+ file_name = nil;
113
+ defaults.each {|n| file_name = n if(File.exist?(n))}
114
+ file_name ||= defaults[-1]
249
115
  end
250
- return app, opt
116
+ file_name = nil if(!File.exist?(file_name))
117
+ raise("Web App not found: #{(Iodine::Base::CLI[0].nil? || File.directory?(Iodine::Base::CLI[0])) ? "missing config.#{Iodine::Base::CLI['-R'] ? '' : 'n'}ru?" : "#{Iodine::Base::CLI[0]} not found"}") if (file_name.nil? && (!Iodine::Base::CLI[0].nil? || Iodine::Base::CLI['-www'].nil?))
118
+ file_name
251
119
  end
252
-
253
- def self.perform_warmup(app)
254
- # load anything marked with `autoload`, since autoload is niether thread safe nor fork friendly.
255
- Iodine.on_state(:on_start) do
256
- Module.constants.each do |n|
257
- begin
258
- Object.const_get(n)
259
- rescue StandardError => _e
260
- end
261
- end
262
- Iodine::Base::Rack::Builder.new(app) do |r|
263
- r.warmup do |a|
264
- client = ::Rack::MockRequest.new(a)
265
- client.get('/')
120
+ def self.iodine___load_rack fn
121
+ return nil unless fn
122
+ return nil unless (Iodine::Base::CLI['-R'] || File.extname(fn) == '.ru')
123
+ begin
124
+ require 'rack'
125
+ rescue LoadError => e
126
+ return nil unless Iodine::Base::CLI['-R']
127
+ puts "ERROR: rack not installed."
128
+ puts " iodine -R requires the rack gem to be installed.\n"
129
+ raise e
130
+ end
131
+ Iodine::Utils.monkey_patch ::Rack::Utils
132
+ app = ::Rack::Builder.parse_file(fn)
133
+ app = app[0] if(app.is_a?(Array)) # Rack 2.x vs. Rack 3.x
134
+ app = {'/' => app} if app
135
+ end
136
+ def self.iodine___load_neorack fn
137
+ return nil unless fn
138
+ cfgfile = ::File.read(fn)
139
+ cfgfile.slice!(/\A#{UTF_8_BOM}/) if cfgfile.encoding == Encoding::UTF_8
140
+ cfgfile.sub!(/^__END__\n.*\Z/m, '')
141
+ builder = self.new
142
+ builder.instance_eval(cfgfile, fn, 0)
143
+ builder.app
144
+ end
145
+ def self.iodine___load_app_code(listener, fn, root_pid)
146
+ err = "Web App not found (no `run`?) in #{fn}"
147
+ app = nil
148
+ if(fn)
149
+ begin
150
+ old_wd = Dir.getwd
151
+ fn_name = File.basename(fn)
152
+ Dir.chdir(File.dirname(fn))
153
+ require 'bundler/setup' if File.exist?('Gemfile') && !defined?(::Bundler)
154
+ app = ::Iodine::Base::NeoRackUp.iodine___load_rack(fn_name)
155
+ unless app || Iodine::Base::CLI['-R']
156
+ app = ::Iodine::Base::NeoRackUp.iodine___load_neorack(fn_name)
266
157
  end
158
+ Dir.chdir(old_wd)
159
+ rescue Exception => e
160
+ err = e
161
+ app = nil
267
162
  end
268
163
  end
164
+ unless !fn || app
165
+ Iodine.stop
166
+ Process.kill("TERM", root_pid)
167
+ raise(err)
168
+ end
169
+ if app && app.is_a?(Hash) && app['/']
170
+ app.each {|k,v| listener.map(k,v) }
171
+ elsif app
172
+ listener.map nil, app
173
+ else
174
+ puts "WARNING: (#{Process.pid}) no App found."
175
+ end
269
176
  end
270
-
271
- def self.call
272
- app, opt = get_app_opts
273
- perform_warmup(app) if Iodine::DEFAULT_SETTINGS[:warmup_]
274
- Iodine::Rack.run(app, opt)
177
+ ### loads auto-detected configuration file (iodine.rb)
178
+ def self.iodine___load_conf(fn = "./")
179
+ old_wd = Dir.getwd
180
+ Dir.chdir(File.dirname(fn)) if(fn && File.dirname(fn))
181
+ conf = Iodine::Base::CLI['--config']
182
+ conf = (::Pathname.new(File.dirname(fn.to_s).to_s) + Iodine::Base::CLI['--config']).to_s if(!conf || !File.exist?(conf))
183
+ unless File.exist?(conf)
184
+ puts "* No configuration file found @ #{conf}"
185
+ Dir.chdir(old_wd)
186
+ return
187
+ end
188
+ puts "* Pre-loading configuration @ #{conf}"
189
+ load conf
190
+ Dir.chdir(old_wd)
191
+ end
192
+ def self.iodine___start
193
+ fn = ::Iodine::Base::NeoRackUp.iodine___detect_filename
194
+ raise "No App and no Static File Service (-www) detected." if(fn.nil? && !Iodine::Base::CLI['-www'])
195
+ ### load configuration file
196
+ ::Iodine::Base::NeoRackUp.iodine___load_conf(fn)
197
+ puts "* Starting iodine @ #{fn ? fn : Iodine::Base::CLI['-www']}"
198
+ listener = Iodine.listen(nil, Iodine::Base::NeoRackUp::NOOPHandler)
199
+ root_pid = Process.pid
200
+ ### hot code reloading?
201
+ if Iodine::Base::CLI['-preload']
202
+ iodine___load_app_code(listener, fn, root_pid)
203
+ else
204
+ Iodine.on_state(:start) do
205
+ iodine___load_app_code(listener, fn, root_pid)
206
+ end
207
+ end
208
+ Iodine.start
275
209
  end
276
210
  end
277
211
  end
278
212
  end
279
213
 
280
- Iodine::Base::CLI.call
214
+ ### .pid file
215
+ if Iodine::Base::CLI['-pid']
216
+ pid_filename = Iodine::Base::CLI['-pid']
217
+ pid_filename << "iodine.pid" if(pid_filename[-1] == '/')
218
+ if File.exist?(pid_filename)
219
+ raise "pid filename should point to a valid file name (not a folder)!" if(!File.file?(pid_filename))
220
+ raise "pid filename error! (doesn't end with .pid)" if(File.extname(pid_filename) != '.pid')
221
+ File.delete(pid_filename)
222
+ end
223
+ Iodine.on_state(:pre_start) do
224
+ IO.binwrite(pid_filename, "#{Process.pid}\r\n")
225
+ end
226
+ Iodine.on_state(:exit) do
227
+ File.delete(pid_filename)
228
+ end
229
+ end
230
+ ### hot restarts
231
+ if Iodine::Base::CLI['-hr'] && Iodine::Base::CLI['-w'].to_i != 0
232
+ Iodine.run_after(Iodine::Base::CLI['-hr'] * 1000, -1) do
233
+ Iodine.master? ? Process.kill("USR1", Process.pid) : false
234
+ end
235
+ end
236
+
237
+ ### Start
238
+ Iodine::Base::NeoRackUp.iodine___start
239
+
240
+
@@ -1,110 +1,37 @@
1
1
  require 'mkmf'
2
- require 'fileutils'
3
2
 
4
- # Test polling
5
- def iodine_test_polling_support
6
- iodine_poll_test_kqueue = <<EOS
7
- \#define _GNU_SOURCE
8
- \#include <stdlib.h>
9
- \#include <sys/event.h>
10
- int main(void) {
11
- int fd = kqueue();
12
- }
13
- EOS
14
-
15
- iodine_poll_test_epoll = <<EOS
16
- \#define _GNU_SOURCE
17
- \#include <stdlib.h>
18
- \#include <stdio.h>
19
- \#include <sys/types.h>
20
- \#include <sys/stat.h>
21
- \#include <fcntl.h>
22
- \#include <sys/epoll.h>
23
- int main(void) {
24
- int fd = epoll_create1(EPOLL_CLOEXEC);
25
- }
26
- EOS
27
-
28
- iodine_poll_test_poll = <<EOS
29
- \#define _GNU_SOURCE
30
- \#include <stdlib.h>
31
- \#include <poll.h>
32
- int main(void) {
33
- struct pollfd plist[18];
34
- memset(plist, 0, sizeof(plist[0]) * 18);
35
- poll(plist, 1, 1);
36
- }
37
- EOS
38
-
39
- # Test for manual selection and then TRY_COMPILE with each polling engine
40
- if Gem.win_platform?
41
- puts "skipping polling tests, using WSAPOLL on Windows"
42
- $defs << "-DFIO_ENGINE_WSAPOLL"
43
- elsif ENV['FIO_POLL']
44
- puts "skipping polling tests, enforcing manual selection of: poll"
45
- $defs << "-DFIO_ENGINE_POLL"
46
- elsif ENV['FIO_FORCE_POLL']
47
- puts "skipping polling tests, enforcing manual selection of: poll"
48
- $defs << "-DFIO_ENGINE_POLL"
49
- elsif ENV['FIO_FORCE_EPOLL']
50
- puts "skipping polling tests, enforcing manual selection of: epoll"
51
- $defs << "-DFIO_ENGINE_EPOLL"
52
- elsif ENV['FIO_FORCE_KQUEUE']
53
- puts "* Skipping polling tests, enforcing manual selection of: kqueue"
54
- $defs << "-DFIO_ENGINE_KQUEUE"
55
- elsif try_compile(iodine_poll_test_epoll)
56
- puts "detected `epoll`"
57
- $defs << "-DFIO_ENGINE_EPOLL"
58
- elsif try_compile(iodine_poll_test_kqueue)
59
- puts "detected `kqueue`"
60
- $defs << "-DFIO_ENGINE_KQUEUE"
61
- elsif try_compile(iodine_poll_test_poll)
62
- puts "detected `poll` - this is suboptimal fallback!"
63
- $defs << "-DFIO_ENGINE_POLL"
3
+ # TLS backend selection:
4
+ # - IODINE_USE_EMBEDDED_TLS=1 forces embedded TLS 1.3 as default
5
+ # - Otherwise, OpenSSL is used if available and compatible
6
+ begin
7
+ dir_config('openssl')
8
+ if ENV['IODINE_USE_EMBEDDED_TLS']
9
+ puts '* Default TLS: embedded (fio_tls13) - forced by IODINE_USE_EMBEDDED_TLS'
10
+ elsif have_library('crypto') && have_library('ssl')
11
+ require 'openssl'
12
+ if OpenSSL::VERSION.to_i > 2
13
+ puts "* Detected OpenSSL version >= 3 (#{OpenSSL::VERSION}), setting the HAVE_OPENSSL flag."
14
+ $defs << '-DHAVE_OPENSSL'
15
+ else
16
+ puts "* OpenSSL version incompatible (#{OpenSSL::VERSION}), using embedded TLS 1.3"
17
+ end
64
18
  else
65
- puts "* WARNING: No supported polling engine! expecting compilation to fail."
19
+ puts '* OpenSSL not found, using embedded TLS 1.3'
66
20
  end
21
+ rescue StandardError => e
22
+ puts "* Couldn't find OpenSSL - using embedded TLS 1.3\n\t Err: #{e.message}"
67
23
  end
68
24
 
69
- iodine_test_polling_support()
25
+ $defs << '-DDEBUG' if ENV['DEBUG']
70
26
 
71
- unless Gem.win_platform?
72
- # Test for OpenSSL version equal to 1.0.0 or greater.
73
- unless ENV['NO_SSL'] || ENV['NO_TLS'] || ENV["DISABLE_SSL"]
74
- OPENSSL_TEST_CODE = <<EOS
75
- \#include <openssl/bio.h>
76
- \#include <openssl/err.h>
77
- \#include <openssl/ssl.h>
78
- \#if OPENSSL_VERSION_NUMBER < 0x10100000L
79
- \#error "OpenSSL version too small"
80
- \#endif
81
- int main(void) {
82
- SSL_library_init();
83
- SSL_CTX *ctx = SSL_CTX_new(TLS_method());
84
- SSL *ssl = SSL_new(ctx);
85
- BIO *bio = BIO_new_socket(3, 0);
86
- BIO_up_ref(bio);
87
- SSL_set0_rbio(ssl, bio);
88
- SSL_set0_wbio(ssl, bio);
89
- }
90
- EOS
27
+ append_cflags('-Wno-undef')
28
+ append_cflags('-Wno-missing-noreturn')
91
29
 
92
- dir_config("openssl")
93
- begin
94
- require 'openssl'
95
- rescue LoadError
96
- else
97
- if have_library('crypto') && have_library('ssl')
98
- puts "detected OpenSSL library, testing for version and required functions."
99
- if try_compile(OPENSSL_TEST_CODE)
100
- $defs << "-DHAVE_OPENSSL"
101
- puts "confirmed OpenSSL to be version 1.1.0 or above (#{OpenSSL::OPENSSL_LIBRARY_VERSION})...\n* compiling with HAVE_OPENSSL."
102
- else
103
- puts "FAILED: OpenSSL version not supported (#{OpenSSL::OPENSSL_LIBRARY_VERSION} is too old)."
104
- end
105
- end
106
- end
107
- end
30
+ # Windows requires crypt32 for cryptographic primitives used by the TLS backend
31
+ # and bcrypt for the system CSPRNG (`BCryptGenRandom` in `fio_rand_bytes_secure`)
32
+ if /mingw|mswin/ =~ RUBY_PLATFORM
33
+ $libs = append_library($libs, 'crypt32')
34
+ $libs = append_library($libs, 'bcrypt')
108
35
  end
109
36
 
110
- create_makefile 'iodine/iodine_ext'
37
+ create_makefile 'iodine/iodine'