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