rackup 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []