jun-puma 1.0.0-java

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 (87) hide show
  1. checksums.yaml +7 -0
  2. data/History.md +2897 -0
  3. data/LICENSE +29 -0
  4. data/README.md +475 -0
  5. data/bin/puma +10 -0
  6. data/bin/puma-wild +25 -0
  7. data/bin/pumactl +12 -0
  8. data/docs/architecture.md +74 -0
  9. data/docs/compile_options.md +55 -0
  10. data/docs/deployment.md +102 -0
  11. data/docs/fork_worker.md +35 -0
  12. data/docs/images/puma-connection-flow-no-reactor.png +0 -0
  13. data/docs/images/puma-connection-flow.png +0 -0
  14. data/docs/images/puma-general-arch.png +0 -0
  15. data/docs/jungle/README.md +9 -0
  16. data/docs/jungle/rc.d/README.md +74 -0
  17. data/docs/jungle/rc.d/puma +61 -0
  18. data/docs/jungle/rc.d/puma.conf +10 -0
  19. data/docs/kubernetes.md +78 -0
  20. data/docs/nginx.md +80 -0
  21. data/docs/plugins.md +38 -0
  22. data/docs/rails_dev_mode.md +28 -0
  23. data/docs/restart.md +65 -0
  24. data/docs/signals.md +98 -0
  25. data/docs/stats.md +142 -0
  26. data/docs/systemd.md +253 -0
  27. data/docs/testing_benchmarks_local_files.md +150 -0
  28. data/docs/testing_test_rackup_ci_files.md +36 -0
  29. data/ext/puma_http11/PumaHttp11Service.java +17 -0
  30. data/ext/puma_http11/ext_help.h +15 -0
  31. data/ext/puma_http11/extconf.rb +80 -0
  32. data/ext/puma_http11/http11_parser.c +1057 -0
  33. data/ext/puma_http11/http11_parser.h +65 -0
  34. data/ext/puma_http11/http11_parser.java.rl +145 -0
  35. data/ext/puma_http11/http11_parser.rl +149 -0
  36. data/ext/puma_http11/http11_parser_common.rl +54 -0
  37. data/ext/puma_http11/mini_ssl.c +842 -0
  38. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  39. data/ext/puma_http11/org/jruby/puma/Http11.java +228 -0
  40. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +455 -0
  41. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +509 -0
  42. data/ext/puma_http11/puma_http11.c +495 -0
  43. data/lib/puma/app/status.rb +96 -0
  44. data/lib/puma/binder.rb +502 -0
  45. data/lib/puma/cli.rb +247 -0
  46. data/lib/puma/client.rb +682 -0
  47. data/lib/puma/cluster/worker.rb +180 -0
  48. data/lib/puma/cluster/worker_handle.rb +96 -0
  49. data/lib/puma/cluster.rb +616 -0
  50. data/lib/puma/commonlogger.rb +115 -0
  51. data/lib/puma/configuration.rb +390 -0
  52. data/lib/puma/const.rb +307 -0
  53. data/lib/puma/control_cli.rb +316 -0
  54. data/lib/puma/detect.rb +45 -0
  55. data/lib/puma/dsl.rb +1425 -0
  56. data/lib/puma/error_logger.rb +113 -0
  57. data/lib/puma/events.rb +57 -0
  58. data/lib/puma/io_buffer.rb +46 -0
  59. data/lib/puma/jruby_restart.rb +11 -0
  60. data/lib/puma/json_serialization.rb +96 -0
  61. data/lib/puma/launcher/bundle_pruner.rb +104 -0
  62. data/lib/puma/launcher.rb +488 -0
  63. data/lib/puma/log_writer.rb +147 -0
  64. data/lib/puma/minissl/context_builder.rb +96 -0
  65. data/lib/puma/minissl.rb +459 -0
  66. data/lib/puma/null_io.rb +84 -0
  67. data/lib/puma/plugin/systemd.rb +90 -0
  68. data/lib/puma/plugin/tmp_restart.rb +36 -0
  69. data/lib/puma/plugin.rb +111 -0
  70. data/lib/puma/puma_http11.jar +0 -0
  71. data/lib/puma/rack/builder.rb +297 -0
  72. data/lib/puma/rack/urlmap.rb +93 -0
  73. data/lib/puma/rack_default.rb +24 -0
  74. data/lib/puma/reactor.rb +125 -0
  75. data/lib/puma/request.rb +688 -0
  76. data/lib/puma/runner.rb +213 -0
  77. data/lib/puma/sd_notify.rb +149 -0
  78. data/lib/puma/server.rb +680 -0
  79. data/lib/puma/single.rb +69 -0
  80. data/lib/puma/state_file.rb +68 -0
  81. data/lib/puma/thread_pool.rb +434 -0
  82. data/lib/puma/util.rb +141 -0
  83. data/lib/puma.rb +78 -0
  84. data/lib/rack/handler/puma.rb +144 -0
  85. data/tools/Dockerfile +16 -0
  86. data/tools/trickletest.rb +44 -0
  87. metadata +153 -0
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../plugin'
4
+
5
+ # Puma's systemd integration allows Puma to inform systemd:
6
+ # 1. when it has successfully started
7
+ # 2. when it is starting shutdown
8
+ # 3. periodically for a liveness check with a watchdog thread
9
+ # 4. periodically set the status
10
+ Puma::Plugin.create do
11
+ def start(launcher)
12
+ require_relative '../sd_notify'
13
+
14
+ launcher.log_writer.log "* Enabling systemd notification integration"
15
+
16
+ # hook_events
17
+ launcher.events.on_booted { Puma::SdNotify.ready }
18
+ launcher.events.on_stopped { Puma::SdNotify.stopping }
19
+ launcher.events.on_restart { Puma::SdNotify.reloading }
20
+
21
+ # start watchdog
22
+ if Puma::SdNotify.watchdog?
23
+ ping_f = watchdog_sleep_time
24
+
25
+ in_background do
26
+ launcher.log_writer.log "Pinging systemd watchdog every #{ping_f.round(1)} sec"
27
+ loop do
28
+ sleep ping_f
29
+ Puma::SdNotify.watchdog
30
+ end
31
+ end
32
+ end
33
+
34
+ # start status loop
35
+ instance = self
36
+ sleep_time = 1.0
37
+ in_background do
38
+ launcher.log_writer.log "Sending status to systemd every #{sleep_time.round(1)} sec"
39
+
40
+ loop do
41
+ sleep sleep_time
42
+ # TODO: error handling?
43
+ Puma::SdNotify.status(instance.status)
44
+ end
45
+ end
46
+ end
47
+
48
+ def status
49
+ if clustered?
50
+ messages = stats[:worker_status].map do |worker|
51
+ common_message(worker[:last_status])
52
+ end.join(',')
53
+
54
+ "Puma #{Puma::Const::VERSION}: cluster: #{booted_workers}/#{workers}, worker_status: [#{messages}]"
55
+ else
56
+ "Puma #{Puma::Const::VERSION}: worker: #{common_message(stats)}"
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ def watchdog_sleep_time
63
+ usec = Integer(ENV["WATCHDOG_USEC"])
64
+
65
+ sec_f = usec / 1_000_000.0
66
+ # "It is recommended that a daemon sends a keep-alive notification message
67
+ # to the service manager every half of the time returned here."
68
+ sec_f / 2
69
+ end
70
+
71
+ def stats
72
+ Puma.stats_hash
73
+ end
74
+
75
+ def clustered?
76
+ stats.has_key?(:workers)
77
+ end
78
+
79
+ def workers
80
+ stats.fetch(:workers, 1)
81
+ end
82
+
83
+ def booted_workers
84
+ stats.fetch(:booted_workers, 1)
85
+ end
86
+
87
+ def common_message(stats)
88
+ "{ #{stats[:running]}/#{stats[:max_threads]} threads, #{stats[:pool_capacity]} available, #{stats[:backlog]} backlog }"
89
+ end
90
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../plugin'
4
+
5
+ Puma::Plugin.create do
6
+ def start(launcher)
7
+ path = File.join("tmp", "restart.txt")
8
+
9
+ orig = nil
10
+
11
+ # If we can't write to the path, then just don't bother with this plugin
12
+ begin
13
+ File.write(path, "") unless File.exist?(path)
14
+ orig = File.stat(path).mtime
15
+ rescue SystemCallError
16
+ return
17
+ end
18
+
19
+ in_background do
20
+ while true
21
+ sleep 2
22
+
23
+ begin
24
+ mtime = File.stat(path).mtime
25
+ rescue SystemCallError
26
+ # If the file has disappeared, assume that means don't restart
27
+ else
28
+ if mtime > orig
29
+ launcher.restart
30
+ break
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Puma
4
+ class UnknownPlugin < RuntimeError; end
5
+
6
+ class PluginLoader
7
+ def initialize
8
+ @instances = []
9
+ end
10
+
11
+ def create(name)
12
+ if cls = Plugins.find(name)
13
+ plugin = cls.new
14
+ @instances << plugin
15
+ return plugin
16
+ end
17
+
18
+ raise UnknownPlugin, "File failed to register properly named plugin"
19
+ end
20
+
21
+ def fire_starts(launcher)
22
+ @instances.each do |i|
23
+ if i.respond_to? :start
24
+ i.start(launcher)
25
+ end
26
+ end
27
+ end
28
+ end
29
+
30
+ class PluginRegistry
31
+ def initialize
32
+ @plugins = {}
33
+ @background = []
34
+ end
35
+
36
+ def register(name, cls)
37
+ @plugins[name] = cls
38
+ end
39
+
40
+ def find(name)
41
+ name = name.to_s
42
+
43
+ if cls = @plugins[name]
44
+ return cls
45
+ end
46
+
47
+ begin
48
+ require "puma/plugin/#{name}"
49
+ rescue LoadError
50
+ raise UnknownPlugin, "Unable to find plugin: #{name}"
51
+ end
52
+
53
+ if cls = @plugins[name]
54
+ return cls
55
+ end
56
+
57
+ raise UnknownPlugin, "file failed to register a plugin"
58
+ end
59
+
60
+ def add_background(blk)
61
+ @background << blk
62
+ end
63
+
64
+ def fire_background
65
+ @background.each_with_index do |b, i|
66
+ Thread.new do
67
+ Puma.set_thread_name "plgn bg #{i}"
68
+ b.call
69
+ end
70
+ end
71
+ end
72
+ end
73
+
74
+ Plugins = PluginRegistry.new
75
+
76
+ class Plugin
77
+ # Matches
78
+ # "C:/Ruby22/lib/ruby/gems/2.2.0/gems/puma-3.0.1/lib/puma/plugin/tmp_restart.rb:3:in `<top (required)>'"
79
+ # AS
80
+ # C:/Ruby22/lib/ruby/gems/2.2.0/gems/puma-3.0.1/lib/puma/plugin/tmp_restart.rb
81
+ CALLER_FILE = /
82
+ \A # start of string
83
+ .+ # file path (one or more characters)
84
+ (?= # stop previous match when
85
+ :\d+ # a colon is followed by one or more digits
86
+ :in # followed by a colon followed by in
87
+ )
88
+ /x
89
+
90
+ def self.extract_name(ary)
91
+ path = ary.first[CALLER_FILE]
92
+
93
+ m = %r!puma/plugin/([^/]*)\.rb$!.match(path)
94
+ m[1]
95
+ end
96
+
97
+ def self.create(&blk)
98
+ name = extract_name(caller)
99
+
100
+ cls = Class.new(self)
101
+
102
+ cls.class_eval(&blk)
103
+
104
+ Plugins.register name, cls
105
+ end
106
+
107
+ def in_background(&blk)
108
+ Plugins.add_background blk
109
+ end
110
+ end
111
+ end
Binary file
@@ -0,0 +1,297 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Puma
4
+ end
5
+
6
+ module Puma::Rack
7
+ class Options
8
+ def parse!(args)
9
+ options = {}
10
+ opt_parser = OptionParser.new("", 24, ' ') do |opts|
11
+ opts.banner = "Usage: rackup [ruby options] [rack options] [rackup config]"
12
+
13
+ opts.separator ""
14
+ opts.separator "Ruby options:"
15
+
16
+ lineno = 1
17
+ opts.on("-e", "--eval LINE", "evaluate a LINE of code") { |line|
18
+ eval line, TOPLEVEL_BINDING, "-e", lineno
19
+ lineno += 1
20
+ }
21
+
22
+ opts.on("-b", "--builder BUILDER_LINE", "evaluate a BUILDER_LINE of code as a builder script") { |line|
23
+ options[:builder] = line
24
+ }
25
+
26
+ opts.on("-d", "--debug", "set debugging flags (set $DEBUG to true)") {
27
+ options[:debug] = true
28
+ }
29
+ opts.on("-w", "--warn", "turn warnings on for your script") {
30
+ options[:warn] = true
31
+ }
32
+ opts.on("-q", "--quiet", "turn off logging") {
33
+ options[:quiet] = true
34
+ }
35
+
36
+ opts.on("-I", "--include PATH",
37
+ "specify $LOAD_PATH (may be used more than once)") { |path|
38
+ (options[:include] ||= []).concat(path.split(":"))
39
+ }
40
+
41
+ opts.on("-r", "--require LIBRARY",
42
+ "require the library, before executing your script") { |library|
43
+ options[:require] = library
44
+ }
45
+
46
+ opts.separator ""
47
+ opts.separator "Rack options:"
48
+ opts.on("-s", "--server SERVER", "serve using SERVER (thin/puma/webrick/mongrel)") { |s|
49
+ options[:server] = s
50
+ }
51
+
52
+ opts.on("-o", "--host HOST", "listen on HOST (default: localhost)") { |host|
53
+ options[:Host] = host
54
+ }
55
+
56
+ opts.on("-p", "--port PORT", "use PORT (default: 9292)") { |port|
57
+ options[:Port] = port
58
+ }
59
+
60
+ opts.on("-O", "--option NAME[=VALUE]", "pass VALUE to the server as option NAME. If no VALUE, sets it to true. Run '#{$0} -s SERVER -h' to get a list of options for SERVER") { |name|
61
+ name, value = name.split('=', 2)
62
+ value = true if value.nil?
63
+ options[name.to_sym] = value
64
+ }
65
+
66
+ opts.on("-E", "--env ENVIRONMENT", "use ENVIRONMENT for defaults (default: development)") { |e|
67
+ options[:environment] = e
68
+ }
69
+
70
+ opts.on("-P", "--pid FILE", "file to store PID") { |f|
71
+ options[:pid] = ::File.expand_path(f)
72
+ }
73
+
74
+ opts.separator ""
75
+ opts.separator "Common options:"
76
+
77
+ opts.on_tail("-h", "-?", "--help", "Show this message") do
78
+ puts opts
79
+ puts handler_opts(options)
80
+
81
+ exit
82
+ end
83
+
84
+ opts.on_tail("--version", "Show version") do
85
+ puts "Rack #{Rack.version} (Release: #{Rack.release})"
86
+ exit
87
+ end
88
+ end
89
+
90
+ begin
91
+ opt_parser.parse! args
92
+ rescue OptionParser::InvalidOption => e
93
+ warn e.message
94
+ abort opt_parser.to_s
95
+ end
96
+
97
+ options[:config] = args.last if args.last
98
+ options
99
+ end
100
+
101
+ def handler_opts(options)
102
+ begin
103
+ info = []
104
+ server = Rack::Handler.get(options[:server]) || Rack::Handler.default(options)
105
+ if server&.respond_to?(:valid_options)
106
+ info << ""
107
+ info << "Server-specific options for #{server.name}:"
108
+
109
+ has_options = false
110
+ server.valid_options.each do |name, description|
111
+ next if /^(Host|Port)[^a-zA-Z]/.match? name.to_s # ignore handler's host and port options, we do our own.
112
+
113
+ info << " -O %-21s %s" % [name, description]
114
+ has_options = true
115
+ end
116
+ return "" if !has_options
117
+ end
118
+ info.join("\n")
119
+ rescue NameError
120
+ return "Warning: Could not find handler specified (#{options[:server] || 'default'}) to determine handler-specific options"
121
+ end
122
+ end
123
+ end
124
+
125
+ # Rack::Builder implements a small DSL to iteratively construct Rack
126
+ # applications.
127
+ #
128
+ # Example:
129
+ #
130
+ # require 'rack/lobster'
131
+ # app = Rack::Builder.new do
132
+ # use Rack::CommonLogger
133
+ # use Rack::ShowExceptions
134
+ # map "/lobster" do
135
+ # use Rack::Lint
136
+ # run Rack::Lobster.new
137
+ # end
138
+ # end
139
+ #
140
+ # run app
141
+ #
142
+ # Or
143
+ #
144
+ # app = Rack::Builder.app do
145
+ # use Rack::CommonLogger
146
+ # run lambda { |env| [200, {'Content-Type' => 'text/plain'}, ['OK']] }
147
+ # end
148
+ #
149
+ # run app
150
+ #
151
+ # +use+ adds middleware to the stack, +run+ dispatches to an application.
152
+ # You can use +map+ to construct a Rack::URLMap in a convenient way.
153
+
154
+ class Builder
155
+ def self.parse_file(config, opts = Options.new)
156
+ options = {}
157
+ if config =~ /\.ru$/
158
+ cfgfile = ::File.read(config)
159
+ if cfgfile[/^#\\(.*)/] && opts
160
+ options = opts.parse! $1.split(/\s+/)
161
+ end
162
+ cfgfile.sub!(/^__END__\n.*\Z/m, '')
163
+ app = new_from_string cfgfile, config
164
+ else
165
+ require config
166
+ app = Object.const_get(::File.basename(config, '.rb').capitalize)
167
+ end
168
+ [app, options]
169
+ end
170
+
171
+ def self.new_from_string(builder_script, file="(rackup)")
172
+ eval "Puma::Rack::Builder.new {\n" + builder_script + "\n}.to_app",
173
+ TOPLEVEL_BINDING, file, 0
174
+ end
175
+
176
+ def initialize(default_app = nil, &block)
177
+ @use, @map, @run, @warmup = [], nil, default_app, nil
178
+
179
+ # Conditionally load rack now, so that any rack middlewares,
180
+ # etc are available.
181
+ begin
182
+ require 'rack'
183
+ rescue LoadError
184
+ end
185
+
186
+ instance_eval(&block) if block
187
+ end
188
+
189
+ def self.app(default_app = nil, &block)
190
+ self.new(default_app, &block).to_app
191
+ end
192
+
193
+ # Specifies middleware to use in a stack.
194
+ #
195
+ # class Middleware
196
+ # def initialize(app)
197
+ # @app = app
198
+ # end
199
+ #
200
+ # def call(env)
201
+ # env["rack.some_header"] = "setting an example"
202
+ # @app.call(env)
203
+ # end
204
+ # end
205
+ #
206
+ # use Middleware
207
+ # run lambda { |env| [200, { "Content-Type" => "text/plain" }, ["OK"]] }
208
+ #
209
+ # All requests through to this application will first be processed by the middleware class.
210
+ # The +call+ method in this example sets an additional environment key which then can be
211
+ # referenced in the application if required.
212
+ def use(middleware, *args, &block)
213
+ if @map
214
+ mapping, @map = @map, nil
215
+ @use << proc { |app| generate_map app, mapping }
216
+ end
217
+ @use << proc { |app| middleware.new(app, *args, &block) }
218
+ end
219
+
220
+ # Takes an argument that is an object that responds to #call and returns a Rack response.
221
+ # The simplest form of this is a lambda object:
222
+ #
223
+ # run lambda { |env| [200, { "Content-Type" => "text/plain" }, ["OK"]] }
224
+ #
225
+ # However this could also be a class:
226
+ #
227
+ # class Heartbeat
228
+ # def self.call(env)
229
+ # [200, { "Content-Type" => "text/plain" }, ["OK"]]
230
+ # end
231
+ # end
232
+ #
233
+ # run Heartbeat
234
+ def run(app)
235
+ @run = app
236
+ end
237
+
238
+ # Takes a lambda or block that is used to warm-up the application.
239
+ #
240
+ # warmup do |app|
241
+ # client = Rack::MockRequest.new(app)
242
+ # client.get('/')
243
+ # end
244
+ #
245
+ # use SomeMiddleware
246
+ # run MyApp
247
+ def warmup(prc=nil, &block)
248
+ @warmup = prc || block
249
+ end
250
+
251
+ # Creates a route within the application.
252
+ #
253
+ # Rack::Builder.app do
254
+ # map '/' do
255
+ # run Heartbeat
256
+ # end
257
+ # end
258
+ #
259
+ # The +use+ method can also be used here to specify middleware to run under a specific path:
260
+ #
261
+ # Rack::Builder.app do
262
+ # map '/' do
263
+ # use Middleware
264
+ # run Heartbeat
265
+ # end
266
+ # end
267
+ #
268
+ # This example includes a piece of middleware which will run before requests hit +Heartbeat+.
269
+ #
270
+ def map(path, &block)
271
+ @map ||= {}
272
+ @map[path] = block
273
+ end
274
+
275
+ def to_app
276
+ app = @map ? generate_map(@run, @map) : @run
277
+ fail "missing run or map statement" unless app
278
+ app = @use.reverse.inject(app) { |a,e| e[a] }
279
+ @warmup&.call app
280
+ app
281
+ end
282
+
283
+ def call(env)
284
+ to_app.call(env)
285
+ end
286
+
287
+ private
288
+
289
+ def generate_map(default_app, mapping)
290
+ require_relative 'urlmap'
291
+
292
+ mapped = default_app ? {'/' => default_app} : {}
293
+ mapping.each { |r,b| mapped[r] = self.class.new(default_app, &b).to_app }
294
+ URLMap.new(mapped)
295
+ end
296
+ end
297
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Puma::Rack
4
+ # Rack::URLMap takes a hash mapping urls or paths to apps, and
5
+ # dispatches accordingly. Support for HTTP/1.1 host names exists if
6
+ # the URLs start with <tt>http://</tt> or <tt>https://</tt>.
7
+ #
8
+ # URLMap modifies the SCRIPT_NAME and PATH_INFO such that the part
9
+ # relevant for dispatch is in the SCRIPT_NAME, and the rest in the
10
+ # PATH_INFO. This should be taken care of when you need to
11
+ # reconstruct the URL in order to create links.
12
+ #
13
+ # URLMap dispatches in such a way that the longest paths are tried
14
+ # first, since they are most specific.
15
+
16
+ class URLMap
17
+ NEGATIVE_INFINITY = -1.0 / 0.0
18
+ INFINITY = 1.0 / 0.0
19
+
20
+ def initialize(map = {})
21
+ remap(map)
22
+ end
23
+
24
+ def remap(map)
25
+ @mapping = map.map { |location, app|
26
+ if location =~ %r{\Ahttps?://(.*?)(/.*)}
27
+ host, location = $1, $2
28
+ else
29
+ host = nil
30
+ end
31
+
32
+ unless location[0] == ?/
33
+ raise ArgumentError, "paths need to start with /"
34
+ end
35
+
36
+ location = location.chomp('/')
37
+ match = Regexp.new("^#{Regexp.quote(location).gsub('/', '/+')}(.*)", Regexp::NOENCODING)
38
+
39
+ [host, location, match, app]
40
+ }.sort_by do |(host, location, _, _)|
41
+ [host ? -host.size : INFINITY, -location.size]
42
+ end
43
+ end
44
+
45
+ def call(env)
46
+ path = env['PATH_INFO']
47
+ script_name = env['SCRIPT_NAME']
48
+ http_host = env['HTTP_HOST']
49
+ server_name = env['SERVER_NAME']
50
+ server_port = env['SERVER_PORT']
51
+
52
+ is_same_server = casecmp?(http_host, server_name) ||
53
+ casecmp?(http_host, "#{server_name}:#{server_port}")
54
+
55
+ @mapping.each do |host, location, match, app|
56
+ unless casecmp?(http_host, host) \
57
+ || casecmp?(server_name, host) \
58
+ || (!host && is_same_server)
59
+ next
60
+ end
61
+
62
+ next unless m = match.match(path.to_s)
63
+
64
+ rest = m[1]
65
+ next unless !rest || rest.empty? || rest[0] == ?/
66
+
67
+ env['SCRIPT_NAME'] = (script_name + location)
68
+ env['PATH_INFO'] = rest
69
+
70
+ return app.call(env)
71
+ end
72
+
73
+ [404, {'Content-Type' => "text/plain", "X-Cascade" => "pass"}, ["Not Found: #{path}"]]
74
+
75
+ ensure
76
+ env['PATH_INFO'] = path
77
+ env['SCRIPT_NAME'] = script_name
78
+ end
79
+
80
+ private
81
+ def casecmp?(v1, v2)
82
+ # if both nil, or they're the same string
83
+ return true if v1 == v2
84
+
85
+ # if either are nil... (but they're not the same)
86
+ return false if v1.nil?
87
+ return false if v2.nil?
88
+
89
+ # otherwise check they're not case-insensitive the same
90
+ v1.casecmp(v2).zero?
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../rack/handler/puma'
4
+
5
+ # rackup was removed in Rack 3, it is now a separate gem
6
+ if Object.const_defined? :Rackup
7
+ module Rackup
8
+ module Handler
9
+ def self.default(options = {})
10
+ ::Rackup::Handler::Puma
11
+ end
12
+ end
13
+ end
14
+ elsif Object.const_defined?(:Rack) && Rack.release < '3'
15
+ module Rack
16
+ module Handler
17
+ def self.default(options = {})
18
+ ::Rack::Handler::Puma
19
+ end
20
+ end
21
+ end
22
+ else
23
+ raise "Rack 3 must be used with the Rackup gem"
24
+ end