rackup 1.0.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 92bf342afdd0c82df66b17a4d0a18102b82ff4f6fe0d6c0a9c6c448c1833b020
4
- data.tar.gz: '0803347f74697ae828f519c5ec3bb3c9d52aa67605c1cc86c918e460ef3b2f22'
3
+ metadata.gz: c58b0cded0e6edaba53fc8bd56f6b86b362feb6c96933ad81ee31f618015adcb
4
+ data.tar.gz: e5bf647ba5b1108790c63ed32dd9684f948fa7d6347359bf70ad39497ac5e144
5
5
  SHA512:
6
- metadata.gz: ccc75df436ba2fba6a7971d0eb0db09714cf90364eeca96d380f3cc86b57845e5d4c38fb1bb9d91cce47207656926de42ebec03d2688dad292fc00c48f76dcf0
7
- data.tar.gz: 074af927d0ed8d981ad74d02139e7493269d6406b3426675aab136d051e4d93e7355f98ca5ae4081458e35572efa8360db47fe657e6dce6ab57a7cf711bae626
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.0"
7
+ VERSION = "2.0.0"
8
8
  end
data/lib/rackup.rb CHANGED
@@ -1,7 +1,7 @@
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
  require_relative 'rackup/handler'
7
7
  require_relative 'rackup/server'
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.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
@@ -15,14 +15,14 @@ dependencies:
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