rackup 0.1.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: a8147a54f57ff6cc1143ffb8b1d56284e4a587ca63aeeb21e491d83348d9dfcd
4
+ data.tar.gz: 438c92e88aee281963b9a796c716feab222e6a16d34533f89278c42f8be1c34f
5
+ SHA512:
6
+ metadata.gz: 918d8410462f54b58b63defeb39b95fae57ae7da05f19dfee0479718d0b4422909b13a00c1a9f3e19f7e5fb4328b4240db0f7bbbc89d77f0df5e2d8a8e8b94f4
7
+ data.tar.gz: c821f8a16afb6946aea5dee3bb0a9b070f782df68dfd979772c612dab61d5c983b3fceb32567ce24fac5aff4627157dfea35fee643170bf4ac75d92497308b52
data/bin/rackup ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "../lib/rackup"
5
+ Rackup::Server.start
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rackup
4
+ module Handler
5
+ class CGI
6
+ include Rack
7
+
8
+ def self.run(app, **options)
9
+ $stdin.binmode
10
+ serve app
11
+ end
12
+
13
+ def self.serve(app)
14
+ env = ENV.to_hash
15
+ env.delete "HTTP_CONTENT_LENGTH"
16
+
17
+ env[SCRIPT_NAME] = "" if env[SCRIPT_NAME] == "/"
18
+
19
+ env.update(
20
+ RACK_INPUT => $stdin,
21
+ RACK_ERRORS => $stderr,
22
+ RACK_URL_SCHEME => ["yes", "on", "1"].include?(ENV[HTTPS]) ? "https" : "http"
23
+ )
24
+
25
+ env[QUERY_STRING] ||= ""
26
+ env[REQUEST_PATH] ||= "/"
27
+
28
+ status, headers, body = app.call(env)
29
+ begin
30
+ send_headers status, headers
31
+ send_body body
32
+ ensure
33
+ body.close if body.respond_to? :close
34
+ end
35
+ end
36
+
37
+ def self.send_headers(status, headers)
38
+ $stdout.print "Status: #{status}\r\n"
39
+ headers.each { |k, vs|
40
+ vs.split("\n").each { |v|
41
+ $stdout.print "#{k}: #{v}\r\n"
42
+ }
43
+ }
44
+ $stdout.print "\r\n"
45
+ $stdout.flush
46
+ end
47
+
48
+ def self.send_body(body)
49
+ body.each { |part|
50
+ $stdout.print part
51
+ $stdout.flush
52
+ }
53
+ end
54
+ end
55
+
56
+ register :cgi, CGI
57
+ end
58
+ end
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'webrick'
4
+ require 'stringio'
5
+
6
+ require 'rack/constants'
7
+ require_relative '../handler'
8
+ require_relative '../version'
9
+
10
+ # This monkey patch allows for applications to perform their own chunking
11
+ # through WEBrick::HTTPResponse if rack is set to true.
12
+ class WEBrick::HTTPResponse
13
+ attr_accessor :rack
14
+
15
+ alias _rack_setup_header setup_header
16
+ def setup_header
17
+ app_chunking = rack && @header['transfer-encoding'] == 'chunked'
18
+
19
+ @chunked = app_chunking if app_chunking
20
+
21
+ _rack_setup_header
22
+
23
+ @chunked = false if app_chunking
24
+ end
25
+ end
26
+
27
+ module Rackup
28
+ module Handler
29
+ class WEBrick < ::WEBrick::HTTPServlet::AbstractServlet
30
+ include Rack
31
+
32
+ def self.run(app, **options)
33
+ environment = ENV['RACK_ENV'] || 'development'
34
+ default_host = environment == 'development' ? 'localhost' : nil
35
+
36
+ if !options[:BindAddress] || options[:Host]
37
+ options[:BindAddress] = options.delete(:Host) || default_host
38
+ end
39
+ options[:Port] ||= 8080
40
+ if options[:SSLEnable]
41
+ require 'webrick/https'
42
+ end
43
+
44
+ @server = ::WEBrick::HTTPServer.new(options)
45
+ @server.mount "/", Rackup::Handler::WEBrick, app
46
+ yield @server if block_given?
47
+ @server.start
48
+ end
49
+
50
+ def self.valid_options
51
+ environment = ENV['RACK_ENV'] || 'development'
52
+ default_host = environment == 'development' ? 'localhost' : '0.0.0.0'
53
+
54
+ {
55
+ "Host=HOST" => "Hostname to listen on (default: #{default_host})",
56
+ "Port=PORT" => "Port to listen on (default: 8080)",
57
+ }
58
+ end
59
+
60
+ def self.shutdown
61
+ if @server
62
+ @server.shutdown
63
+ @server = nil
64
+ end
65
+ end
66
+
67
+ def initialize(server, app)
68
+ super server
69
+ @app = app
70
+ end
71
+
72
+ def service(req, res)
73
+ res.rack = true
74
+ env = req.meta_vars
75
+ env.delete_if { |k, v| v.nil? }
76
+
77
+ rack_input = StringIO.new(req.body.to_s)
78
+ rack_input.set_encoding(Encoding::BINARY)
79
+
80
+ env.update(
81
+ RACK_INPUT => rack_input,
82
+ RACK_ERRORS => $stderr,
83
+ RACK_URL_SCHEME => ["yes", "on", "1"].include?(env[HTTPS]) ? "https" : "http",
84
+ RACK_IS_HIJACK => true,
85
+ )
86
+
87
+ env[QUERY_STRING] ||= ""
88
+ unless env[PATH_INFO] == ""
89
+ path, n = req.request_uri.path, env[SCRIPT_NAME].length
90
+ env[PATH_INFO] = path[n, path.length - n]
91
+ end
92
+ env[REQUEST_PATH] ||= [env[SCRIPT_NAME], env[PATH_INFO]].join
93
+
94
+ status, headers, body = @app.call(env)
95
+ begin
96
+ res.status = status.to_i
97
+ io_lambda = nil
98
+ headers.each { |key, value|
99
+ if key == RACK_HIJACK
100
+ io_lambda = value
101
+ elsif key == "set-cookie"
102
+ res.cookies.concat(Array(value))
103
+ else
104
+ # Since WEBrick won't accept repeated headers,
105
+ # merge the values per RFC 1945 section 4.2.
106
+ res[key] = Array(value).join(", ")
107
+ end
108
+ }
109
+
110
+ if io_lambda
111
+ rd, wr = IO.pipe
112
+ res.body = rd
113
+ res.chunked = true
114
+ io_lambda.call wr
115
+ elsif body.respond_to?(:to_path)
116
+ res.body = ::File.open(body.to_path, 'rb')
117
+ else
118
+ body.each { |part|
119
+ res.body << part
120
+ }
121
+ end
122
+ ensure
123
+ body.close if body.respond_to? :close
124
+ end
125
+ end
126
+ end
127
+
128
+ register :webrick, WEBrick
129
+ end
130
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rack/handler'
4
+
5
+ module Rackup
6
+ # *Handlers* connect web servers with Rack.
7
+ #
8
+ # Rackup includes Handlers for WEBrick and CGI.
9
+ #
10
+ # Handlers usually are activated by calling <tt>MyHandler.run(myapp)</tt>.
11
+ # A second optional hash can be passed to include server-specific
12
+ # configuration.
13
+ module Handler
14
+ def self.[](name)
15
+ begin
16
+ Rack::Handler[name] || self.const_get(name, false)
17
+ rescue NameError
18
+ # Ignore.
19
+ end
20
+ end
21
+
22
+ def self.get(name)
23
+ return nil unless name
24
+
25
+ name = name.to_sym
26
+
27
+ if server = self[name]
28
+ return server
29
+ end
30
+
31
+ require_handler("rack/handler", name)
32
+
33
+ return self[name]
34
+ end
35
+
36
+ def self.register(name, klass)
37
+ name = name.to_sym
38
+
39
+ Rack::Handler.register(name, klass)
40
+ end
41
+
42
+ RACK_HANDLER = 'RACK_HANDLER'
43
+ RACKUP_HANDLER = 'RACKUP_HANDLER'
44
+
45
+ SERVER_NAMES = %i(puma falcon webrick).freeze
46
+ private_constant :SERVER_NAMES
47
+
48
+ # Select first available Rack handler given an `Array` of server names.
49
+ # Raises `LoadError` if no handler was found.
50
+ #
51
+ # > pick ['puma', 'webrick']
52
+ # => Rackup::Handler::WEBrick
53
+ def self.pick(server_names)
54
+ server_names = Array(server_names)
55
+
56
+ server_names.each do |server_name|
57
+ begin
58
+ server = self.get(server_name)
59
+ return server if server
60
+ rescue LoadError
61
+ # Ignore.
62
+ end
63
+ end
64
+
65
+ raise LoadError, "Couldn't find handler for: #{server_names.join(', ')}."
66
+ end
67
+
68
+ def self.default
69
+ if rack_handler = ENV[RACKUP_HANDLER]
70
+ self.get(rack_handler)
71
+ elsif rack_handler = ENV[RACK_HANDLER]
72
+ warn "RACK_HANDLER is deprecated, use RACKUP_HANDLER."
73
+ self.get(rack_handler)
74
+ else
75
+ pick SERVER_NAMES
76
+ end
77
+ end
78
+
79
+ # Transforms server-name constants to their canonical form as filenames,
80
+ # then tries to require them but silences the LoadError if not found
81
+ #
82
+ # Naming convention:
83
+ #
84
+ # Foo # => 'foo'
85
+ # FooBar # => 'foo_bar.rb'
86
+ # FooBAR # => 'foobar.rb'
87
+ # FOObar # => 'foobar.rb'
88
+ # FOOBAR # => 'foobar.rb'
89
+ # FooBarBaz # => 'foo_bar_baz.rb'
90
+ def self.require_handler(prefix, const_name)
91
+ file = const_name.to_s.gsub(/^[A-Z]+/) { |pre| pre.downcase }.
92
+ gsub(/[A-Z]+[^A-Z]/, '_\&').downcase
93
+
94
+ require(::File.join(prefix, file))
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'zlib'
4
+
5
+ require 'rack/constants'
6
+ require 'rack/request'
7
+ require 'rack/response'
8
+
9
+ module Rackup
10
+ # Paste has a Pony, Rack has a Lobster!
11
+ class Lobster
12
+ include Rack
13
+
14
+ LobsterString = Zlib::Inflate.inflate("eJx9kEEOwyAMBO99xd7MAcytUhPlJyj2
15
+ P6jy9i4k9EQyGAnBarEXeCBqSkntNXsi/ZCvC48zGQoZKikGrFMZvgS5ZHd+aGWVuWwhVF0
16
+ t1drVmiR42HcWNz5w3QanT+2gIvTVCiE1lm1Y0eU4JGmIIbaKwextKn8rvW+p5PIwFl8ZWJ
17
+ I8jyiTlhTcYXkekJAzTyYN6E08A+dk8voBkAVTJQ==".delete("\n ").unpack("m*")[0])
18
+
19
+ LambdaLobster = lambda { |env|
20
+ if env[QUERY_STRING].include?("flip")
21
+ lobster = LobsterString.split("\n").
22
+ map { |line| line.ljust(42).reverse }.
23
+ join("\n")
24
+ href = "?"
25
+ else
26
+ lobster = LobsterString
27
+ href = "?flip"
28
+ end
29
+
30
+ content = ["<title>Lobstericious!</title>",
31
+ "<pre>", lobster, "</pre>",
32
+ "<a href='#{href}'>flip!</a>"]
33
+ length = content.inject(0) { |a, e| a + e.size }.to_s
34
+ [200, { CONTENT_TYPE => "text/html", CONTENT_LENGTH => length }, content]
35
+ }
36
+
37
+ def call(env)
38
+ req = Request.new(env)
39
+ if req.GET["flip"] == "left"
40
+ lobster = LobsterString.split("\n").map do |line|
41
+ line.ljust(42).reverse.
42
+ gsub('\\', 'TEMP').
43
+ gsub('/', '\\').
44
+ gsub('TEMP', '/').
45
+ gsub('{', '}').
46
+ gsub('(', ')')
47
+ end.join("\n")
48
+ href = "?flip=right"
49
+ elsif req.GET["flip"] == "crash"
50
+ raise "Lobster crashed"
51
+ else
52
+ lobster = LobsterString
53
+ href = "?flip=left"
54
+ end
55
+
56
+ res = Response.new
57
+ res.write "<title>Lobstericious!</title>"
58
+ res.write "<pre>"
59
+ res.write lobster
60
+ res.write "</pre>"
61
+ res.write "<p><a href='#{href}'>flip!</a></p>"
62
+ res.write "<p><a href='?flip=crash'>crash!</a></p>"
63
+ res.finish
64
+ end
65
+
66
+ end
67
+ end
68
+
69
+ if $0 == __FILE__
70
+ # :nocov:
71
+ require_relative 'server'
72
+ require_relative 'show_exceptions'
73
+ require_relative 'lint'
74
+ Rackup::Server.start(
75
+ app: Rack::ShowExceptions.new(Rack::Lint.new(Rackup::Lobster.new)), Port: 9292
76
+ )
77
+ # :nocov:
78
+ end
@@ -0,0 +1,459 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'optparse'
4
+ require 'fileutils'
5
+
6
+ require 'rack/builder'
7
+ require 'rack/common_logger'
8
+ require 'rack/content_length'
9
+ require 'rack/show_exceptions'
10
+ require 'rack/lint'
11
+ require 'rack/tempfile_reaper'
12
+
13
+ require 'rack/version'
14
+
15
+ require_relative 'version'
16
+ require_relative 'handler'
17
+
18
+ module Rackup
19
+ class Server
20
+ class Options
21
+ def parse!(args)
22
+ options = {}
23
+ opt_parser = OptionParser.new("", 24, ' ') do |opts|
24
+ opts.banner = "Usage: rackup [ruby options] [rack options] [rackup config]"
25
+
26
+ opts.separator ""
27
+ opts.separator "Ruby options:"
28
+
29
+ lineno = 1
30
+ opts.on("-e", "--eval LINE", "evaluate a LINE of code") { |line|
31
+ eval line, TOPLEVEL_BINDING, "-e", lineno
32
+ lineno += 1
33
+ }
34
+
35
+ opts.on("-d", "--debug", "set debugging flags (set $DEBUG to true)") {
36
+ options[:debug] = true
37
+ }
38
+ opts.on("-w", "--warn", "turn warnings on for your script") {
39
+ options[:warn] = true
40
+ }
41
+ opts.on("-q", "--quiet", "turn off logging") {
42
+ options[:quiet] = true
43
+ }
44
+
45
+ opts.on("-I", "--include PATH",
46
+ "specify $LOAD_PATH (may be used more than once)") { |path|
47
+ (options[:include] ||= []).concat(path.split(":"))
48
+ }
49
+
50
+ opts.on("-r", "--require LIBRARY",
51
+ "require the library, before executing your script") { |library|
52
+ (options[:require] ||= []) << library
53
+ }
54
+
55
+ opts.separator ""
56
+ opts.separator "Rack options:"
57
+ opts.on("-b", "--builder BUILDER_LINE", "evaluate a BUILDER_LINE of code as a builder script") { |line|
58
+ options[:builder] = line
59
+ }
60
+
61
+ opts.on("-s", "--server SERVER", "serve using SERVER (thin/puma/webrick)") { |s|
62
+ options[:server] = s
63
+ }
64
+
65
+ opts.on("-o", "--host HOST", "listen on HOST (default: localhost)") { |host|
66
+ options[:Host] = host
67
+ }
68
+
69
+ opts.on("-p", "--port PORT", "use PORT (default: 9292)") { |port|
70
+ options[:Port] = port
71
+ }
72
+
73
+ 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|
74
+ name, value = name.split('=', 2)
75
+ value = true if value.nil?
76
+ options[name.to_sym] = value
77
+ }
78
+
79
+ opts.on("-E", "--env ENVIRONMENT", "use ENVIRONMENT for defaults (default: development)") { |e|
80
+ options[:environment] = e
81
+ }
82
+
83
+ opts.on("-D", "--daemonize", "run daemonized in the background") { |d|
84
+ options[:daemonize] ||= true
85
+ }
86
+
87
+ opts.on("--daemonize-noclose", "run daemonized in the background without closing stdout/stderr") {
88
+ options[:daemonize] = :noclose
89
+ }
90
+
91
+ opts.on("-P", "--pid FILE", "file to store PID") { |f|
92
+ options[:pid] = ::File.expand_path(f)
93
+ }
94
+
95
+ opts.separator ""
96
+ opts.separator "Profiling options:"
97
+
98
+ opts.on("--heap HEAPFILE", "Build the application, then dump the heap to HEAPFILE") do |e|
99
+ options[:heapfile] = e
100
+ end
101
+
102
+ opts.on("--profile PROFILE", "Dump CPU or Memory profile to PROFILE (defaults to a tempfile)") do |e|
103
+ options[:profile_file] = e
104
+ end
105
+
106
+ opts.on("--profile-mode MODE", "Profile mode (cpu|wall|object)") do |e|
107
+ unless %w[cpu wall object].include?(e)
108
+ raise OptionParser::InvalidOption, "unknown profile mode: #{e}"
109
+ end
110
+ options[:profile_mode] = e.to_sym
111
+ end
112
+
113
+ opts.separator ""
114
+ opts.separator "Common options:"
115
+
116
+ opts.on_tail("-h", "-?", "--help", "Show this message") do
117
+ puts opts
118
+ puts handler_opts(options)
119
+
120
+ exit
121
+ end
122
+
123
+ opts.on_tail("--version", "Show version") do
124
+ puts "Rack #{Rack::RELEASE}"
125
+ exit
126
+ end
127
+ end
128
+
129
+ begin
130
+ opt_parser.parse! args
131
+ rescue OptionParser::InvalidOption => e
132
+ warn e.message
133
+ abort opt_parser.to_s
134
+ end
135
+
136
+ options[:config] = args.last if args.last && !args.last.empty?
137
+ options
138
+ end
139
+
140
+ def handler_opts(options)
141
+ info = []
142
+ server = Rackup::Handler.get(options[:server]) || Rackup::Handler.default
143
+ if server && server.respond_to?(:valid_options)
144
+ info << ""
145
+ info << "Server-specific options for #{server.name}:"
146
+
147
+ has_options = false
148
+ server.valid_options.each do |name, description|
149
+ next if /^(Host|Port)[^a-zA-Z]/.match?(name.to_s) # ignore handler's host and port options, we do our own.
150
+ info << sprintf(" -O %-21s %s", name, description)
151
+ has_options = true
152
+ end
153
+ return "" if !has_options
154
+ end
155
+ info.join("\n")
156
+ rescue NameError, LoadError
157
+ return "Warning: Could not find handler specified (#{options[:server] || 'default'}) to determine handler-specific options"
158
+ end
159
+ end
160
+
161
+ # Start a new rack server (like running rackup). This will parse ARGV and
162
+ # provide standard ARGV rackup options, defaulting to load 'config.ru'.
163
+ #
164
+ # Providing an options hash will prevent ARGV parsing and will not include
165
+ # any default options.
166
+ #
167
+ # This method can be used to very easily launch a CGI application, for
168
+ # example:
169
+ #
170
+ # Rack::Server.start(
171
+ # :app => lambda do |e|
172
+ # [200, {'content-type' => 'text/html'}, ['hello world']]
173
+ # end,
174
+ # :server => 'cgi'
175
+ # )
176
+ #
177
+ # Further options available here are documented on Rack::Server#initialize
178
+ def self.start(options = nil)
179
+ new(options).start
180
+ end
181
+
182
+ attr_writer :options
183
+
184
+ # Options may include:
185
+ # * :app
186
+ # a rack application to run (overrides :config and :builder)
187
+ # * :builder
188
+ # a string to evaluate a Rack::Builder from
189
+ # * :config
190
+ # a rackup configuration file path to load (.ru)
191
+ # * :environment
192
+ # this selects the middleware that will be wrapped around
193
+ # your application. Default options available are:
194
+ # - development: CommonLogger, ShowExceptions, and Lint
195
+ # - deployment: CommonLogger
196
+ # - none: no extra middleware
197
+ # note: when the server is a cgi server, CommonLogger is not included.
198
+ # * :server
199
+ # choose a specific Rackup::Handler, e.g. cgi, fcgi, webrick
200
+ # * :daemonize
201
+ # if truthy, the server will daemonize itself (fork, detach, etc)
202
+ # if :noclose, the server will not close STDOUT/STDERR
203
+ # * :pid
204
+ # path to write a pid file after daemonize
205
+ # * :Host
206
+ # the host address to bind to (used by supporting Rackup::Handler)
207
+ # * :Port
208
+ # the port to bind to (used by supporting Rackup::Handler)
209
+ # * :AccessLog
210
+ # webrick access log options (or supporting Rackup::Handler)
211
+ # * :debug
212
+ # turn on debug output ($DEBUG = true)
213
+ # * :warn
214
+ # turn on warnings ($-w = true)
215
+ # * :include
216
+ # add given paths to $LOAD_PATH
217
+ # * :require
218
+ # require the given libraries
219
+ #
220
+ # Additional options for profiling app initialization include:
221
+ # * :heapfile
222
+ # location for ObjectSpace.dump_all to write the output to
223
+ # * :profile_file
224
+ # location for CPU/Memory (StackProf) profile output (defaults to a tempfile)
225
+ # * :profile_mode
226
+ # StackProf profile mode (cpu|wall|object)
227
+ def initialize(options = nil)
228
+ @ignore_options = []
229
+
230
+ if options
231
+ @use_default_options = false
232
+ @options = options
233
+ @app = options[:app] if options[:app]
234
+ else
235
+ @use_default_options = true
236
+ @options = parse_options(ARGV)
237
+ end
238
+ end
239
+
240
+ def options
241
+ merged_options = @use_default_options ? default_options.merge(@options) : @options
242
+ merged_options.reject { |k, v| @ignore_options.include?(k) }
243
+ end
244
+
245
+ def default_options
246
+ environment = ENV['RACK_ENV'] || 'development'
247
+ default_host = environment == 'development' ? 'localhost' : '0.0.0.0'
248
+
249
+ {
250
+ environment: environment,
251
+ pid: nil,
252
+ Port: 9292,
253
+ Host: default_host,
254
+ AccessLog: [],
255
+ config: "config.ru"
256
+ }
257
+ end
258
+
259
+ def app
260
+ @app ||= options[:builder] ? build_app_from_string : build_app_and_options_from_config
261
+ end
262
+
263
+ class << self
264
+ def logging_middleware
265
+ lambda { |server|
266
+ /CGI/.match?(server.server.name) || server.options[:quiet] ? nil : [Rack::CommonLogger, $stderr]
267
+ }
268
+ end
269
+
270
+ def default_middleware_by_environment
271
+ m = Hash.new {|h, k| h[k] = []}
272
+ m["deployment"] = [
273
+ [Rack::ContentLength],
274
+ logging_middleware,
275
+ [Rack::TempfileReaper]
276
+ ]
277
+ m["development"] = [
278
+ [Rack::ContentLength],
279
+ logging_middleware,
280
+ [Rack::ShowExceptions],
281
+ [Rack::Lint],
282
+ [Rack::TempfileReaper]
283
+ ]
284
+
285
+ m
286
+ end
287
+
288
+ def middleware
289
+ default_middleware_by_environment
290
+ end
291
+ end
292
+
293
+ def middleware
294
+ self.class.middleware
295
+ end
296
+
297
+ def start(&block)
298
+ if options[:warn]
299
+ $-w = true
300
+ end
301
+
302
+ if includes = options[:include]
303
+ $LOAD_PATH.unshift(*includes)
304
+ end
305
+
306
+ Array(options[:require]).each do |library|
307
+ require library
308
+ end
309
+
310
+ if options[:debug]
311
+ $DEBUG = true
312
+ require 'pp'
313
+ p options[:server]
314
+ pp wrapped_app
315
+ pp app
316
+ end
317
+
318
+ check_pid! if options[:pid]
319
+
320
+ # Touch the wrapped app, so that the config.ru is loaded before
321
+ # daemonization (i.e. before chdir, etc).
322
+ handle_profiling(options[:heapfile], options[:profile_mode], options[:profile_file]) do
323
+ wrapped_app
324
+ end
325
+
326
+ daemonize_app if options[:daemonize]
327
+
328
+ write_pid if options[:pid]
329
+
330
+ trap(:INT) do
331
+ if server.respond_to?(:shutdown)
332
+ server.shutdown
333
+ else
334
+ exit
335
+ end
336
+ end
337
+
338
+ server.run(wrapped_app, **options, &block)
339
+ end
340
+
341
+ def server
342
+ @_server ||= Handler.get(options[:server]) || Handler.default
343
+ end
344
+
345
+ private
346
+ def build_app_and_options_from_config
347
+ if !::File.exist? options[:config]
348
+ abort "configuration #{options[:config]} not found"
349
+ end
350
+
351
+ return Rack::Builder.parse_file(self.options[:config])
352
+ end
353
+
354
+ def handle_profiling(heapfile, profile_mode, filename)
355
+ if heapfile
356
+ require "objspace"
357
+ ObjectSpace.trace_object_allocations_start
358
+ yield
359
+ GC.start
360
+ ::File.open(heapfile, "w") { |f| ObjectSpace.dump_all(output: f) }
361
+ exit
362
+ end
363
+
364
+ if profile_mode
365
+ require "stackprof"
366
+ require "tempfile"
367
+
368
+ make_profile_name(filename) do |filename|
369
+ ::File.open(filename, "w") do |f|
370
+ StackProf.run(mode: profile_mode, out: f) do
371
+ yield
372
+ end
373
+ puts "Profile written to: #{filename}"
374
+ end
375
+ end
376
+ exit
377
+ end
378
+
379
+ yield
380
+ end
381
+
382
+ def make_profile_name(filename)
383
+ if filename
384
+ yield filename
385
+ else
386
+ ::Dir::Tmpname.create("profile.dump") do |tmpname, _, _|
387
+ yield tmpname
388
+ end
389
+ end
390
+ end
391
+
392
+ def build_app_from_string
393
+ Rack::Builder.new_from_string(self.options[:builder])
394
+ end
395
+
396
+ def parse_options(args)
397
+ # Don't evaluate CGI ISINDEX parameters.
398
+ args.clear if ENV.include?(Rack::REQUEST_METHOD)
399
+
400
+ @options = opt_parser.parse!(args)
401
+ @options[:config] = ::File.expand_path(options[:config])
402
+ ENV["RACK_ENV"] = options[:environment]
403
+ @options
404
+ end
405
+
406
+ def opt_parser
407
+ Options.new
408
+ end
409
+
410
+ def build_app(app)
411
+ middleware[options[:environment]].reverse_each do |middleware|
412
+ middleware = middleware.call(self) if middleware.respond_to?(:call)
413
+ next unless middleware
414
+ klass, *args = middleware
415
+ app = klass.new(app, *args)
416
+ end
417
+ app
418
+ end
419
+
420
+ def wrapped_app
421
+ @wrapped_app ||= build_app app
422
+ end
423
+
424
+ def daemonize_app
425
+ # Cannot be covered as it forks
426
+ # :nocov:
427
+ Process.daemon(true, options[:daemonize] == :noclose)
428
+ # :nocov:
429
+ end
430
+
431
+ def write_pid
432
+ ::File.open(options[:pid], ::File::CREAT | ::File::EXCL | ::File::WRONLY ){ |f| f.write("#{Process.pid}") }
433
+ at_exit { ::FileUtils.rm_f(options[:pid]) }
434
+ rescue Errno::EEXIST
435
+ check_pid!
436
+ retry
437
+ end
438
+
439
+ def check_pid!
440
+ return unless ::File.exist?(options[:pid])
441
+
442
+ pid = ::File.read(options[:pid]).to_i
443
+ raise Errno::ESRCH if pid == 0
444
+
445
+ Process.kill(0, pid)
446
+ exit_with_pid(pid)
447
+ rescue Errno::ESRCH
448
+ ::File.delete(options[:pid])
449
+ rescue Errno::EPERM
450
+ exit_with_pid(pid)
451
+ end
452
+
453
+ def exit_with_pid(pid)
454
+ $stderr.puts "A server is already running (pid: #{pid}, file: #{options[:pid]})."
455
+ exit(1)
456
+ end
457
+ end
458
+
459
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright, 2022, by Samuel G. D. Williams. <http://www.codeotaku.com>
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+ module Rackup
24
+ VERSION = "0.1.0"
25
+ end
data/lib/rackup.rb ADDED
@@ -0,0 +1,6 @@
1
+ require_relative 'rackup/handler'
2
+ require_relative 'rackup/server'
3
+ require_relative 'rackup/version'
4
+
5
+ require_relative 'rackup/handler/webrick'
6
+ require_relative 'rackup/handler/cgi'
metadata ADDED
@@ -0,0 +1,148 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rackup
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Rack Contributors
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-08-03 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rack
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: webrick
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '5.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '5.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: minitest-sprint
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: minitest-global_expectations
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: bundler
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rake
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description:
112
+ email:
113
+ executables: []
114
+ extensions: []
115
+ extra_rdoc_files: []
116
+ files:
117
+ - bin/rackup
118
+ - lib/rackup.rb
119
+ - lib/rackup/handler.rb
120
+ - lib/rackup/handler/cgi.rb
121
+ - lib/rackup/handler/webrick.rb
122
+ - lib/rackup/lobster.rb
123
+ - lib/rackup/server.rb
124
+ - lib/rackup/version.rb
125
+ homepage: https://github.com/rack/rackup
126
+ licenses:
127
+ - MIT
128
+ metadata: {}
129
+ post_install_message:
130
+ rdoc_options: []
131
+ require_paths:
132
+ - lib
133
+ required_ruby_version: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: 2.4.0
138
+ required_rubygems_version: !ruby/object:Gem::Requirement
139
+ requirements:
140
+ - - ">="
141
+ - !ruby/object:Gem::Version
142
+ version: '0'
143
+ requirements: []
144
+ rubygems_version: 3.3.7
145
+ signing_key:
146
+ specification_version: 4
147
+ summary: A general server command for Rack applications.
148
+ test_files: []