rackup 1.0.0 → 2.1.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: a0b31e72267a0dc7a7cc3bbe41d06b49ea16ec3612edc7474fe5c99c97e568cf
4
+ data.tar.gz: 6b9b9f197c6929bed47b7bb3c81f13f956427e1d078ab2a4b12afc0c4400f881
5
5
  SHA512:
6
- metadata.gz: ccc75df436ba2fba6a7971d0eb0db09714cf90364eeca96d380f3cc86b57845e5d4c38fb1bb9d91cce47207656926de42ebec03d2688dad292fc00c48f76dcf0
7
- data.tar.gz: 074af927d0ed8d981ad74d02139e7493269d6406b3426675aab136d051e4d93e7355f98ca5ae4081458e35572efa8360db47fe657e6dce6ab57a7cf711bae626
6
+ metadata.gz: 21aba248befe16cd582ac7c867bc28cecd41155e2f8979e8cecba248e2f1623894c3f943484a85a189d3e2bdec45c836ad8f9f2995a2362809332b1b99dcd3d5
7
+ data.tar.gz: 258c30b02fc3d454df9dcd1d6285f0f3b6d592728f856c53e761c37db2a56925bc0a90bf0b3330441c754fddec280501abc13aa2032a120f3ce5917bd38c817f
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,162 @@
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
+ require_relative '../stream'
15
+
16
+ module Rackup
17
+ module Handler
18
+ class WEBrick < ::WEBrick::HTTPServlet::AbstractServlet
19
+ def self.run(app, **options)
20
+ environment = ENV['RACK_ENV'] || 'development'
21
+ default_host = environment == 'development' ? 'localhost' : nil
22
+
23
+ if !options[:BindAddress] || options[:Host]
24
+ options[:BindAddress] = options.delete(:Host) || default_host
25
+ end
26
+ options[:Port] ||= 8080
27
+ if options[:SSLEnable]
28
+ require 'webrick/https'
29
+ end
30
+
31
+ @server = ::WEBrick::HTTPServer.new(options)
32
+ @server.mount "/", Rackup::Handler::WEBrick, app
33
+ yield @server if block_given?
34
+ @server.start
35
+ end
36
+
37
+ def self.valid_options
38
+ environment = ENV['RACK_ENV'] || 'development'
39
+ default_host = environment == 'development' ? 'localhost' : '0.0.0.0'
40
+
41
+ {
42
+ "Host=HOST" => "Hostname to listen on (default: #{default_host})",
43
+ "Port=PORT" => "Port to listen on (default: 8080)",
44
+ }
45
+ end
46
+
47
+ def self.shutdown
48
+ if @server
49
+ @server.shutdown
50
+ @server = nil
51
+ end
52
+ end
53
+
54
+ def initialize(server, app)
55
+ super server
56
+ @app = app
57
+ end
58
+
59
+ # This handles mapping the WEBrick request to a Rack input stream.
60
+ class Input
61
+ include Stream::Reader
62
+
63
+ def initialize(request)
64
+ @request = request
65
+
66
+ @reader = Fiber.new do
67
+ @request.body do |chunk|
68
+ Fiber.yield(chunk)
69
+ end
70
+
71
+ Fiber.yield(nil)
72
+
73
+ # End of stream:
74
+ @reader = nil
75
+ end
76
+ end
77
+
78
+ def close
79
+ @request = nil
80
+ @reader = nil
81
+ end
82
+
83
+ private
84
+
85
+ # Read one chunk from the request body.
86
+ def read_next
87
+ @reader&.resume
88
+ end
89
+ end
90
+
91
+ def service(req, res)
92
+ env = req.meta_vars
93
+ env.delete_if { |k, v| v.nil? }
94
+
95
+ input = Input.new(req)
96
+
97
+ env.update(
98
+ ::Rack::RACK_INPUT => input,
99
+ ::Rack::RACK_ERRORS => $stderr,
100
+ ::Rack::RACK_URL_SCHEME => ["yes", "on", "1"].include?(env[::Rack::HTTPS]) ? "https" : "http",
101
+ ::Rack::RACK_IS_HIJACK => true,
102
+ )
103
+
104
+ env[::Rack::QUERY_STRING] ||= ""
105
+ unless env[::Rack::PATH_INFO] == ""
106
+ path, n = req.request_uri.path, env[::Rack::SCRIPT_NAME].length
107
+ env[::Rack::PATH_INFO] = path[n, path.length - n]
108
+ end
109
+ env[::Rack::REQUEST_PATH] ||= [env[::Rack::SCRIPT_NAME], env[::Rack::PATH_INFO]].join
110
+
111
+ status, headers, body = @app.call(env)
112
+ begin
113
+ res.status = status
114
+
115
+ if value = headers[::Rack::RACK_HIJACK]
116
+ io_lambda = value
117
+ body = nil
118
+ elsif !body.respond_to?(:to_path) && !body.respond_to?(:each)
119
+ io_lambda = body
120
+ body = nil
121
+ end
122
+
123
+ if value = headers.delete('set-cookie')
124
+ res.cookies.concat(Array(value))
125
+ end
126
+
127
+ headers.each do |key, value|
128
+ # Skip keys starting with rack., per Rack SPEC
129
+ next if key.start_with?('rack.')
130
+
131
+ # Since WEBrick won't accept repeated headers,
132
+ # merge the values per RFC 1945 section 4.2.
133
+ value = value.join(", ") if Array === value
134
+ res[key] = value
135
+ end
136
+
137
+ if io_lambda
138
+ protocol = headers['rack.protocol'] || headers['upgrade']
139
+
140
+ if protocol
141
+ # Set all the headers correctly for an upgrade response:
142
+ res.upgrade!(protocol)
143
+ end
144
+ res.body = io_lambda
145
+ elsif body.respond_to?(:to_path)
146
+ res.body = ::File.open(body.to_path, 'rb')
147
+ else
148
+ buffer = String.new
149
+ body.each do |part|
150
+ buffer << part
151
+ end
152
+ res.body = buffer
153
+ end
154
+ ensure
155
+ body.close if body.respond_to?(:close)
156
+ end
157
+ end
158
+ end
159
+
160
+ register :webrick, WEBrick
161
+ end
162
+ 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
@@ -0,0 +1,199 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2019-2022, by Samuel Williams.
5
+
6
+ module Rackup
7
+ # The input stream is an IO-like object which contains the raw HTTP POST data. When applicable, its external encoding must be “ASCII-8BIT” and it must be opened in binary mode, for Ruby 1.9 compatibility. The input stream must respond to gets, each, read and rewind.
8
+ class Stream
9
+ def initialize(input = nil, output = Buffered.new)
10
+ @input = input
11
+ @output = output
12
+
13
+ raise ArgumentError, "Non-writable output!" unless output.respond_to?(:write)
14
+
15
+ # Will hold remaining data in `#read`.
16
+ @buffer = nil
17
+ @closed = false
18
+ end
19
+
20
+ attr :input
21
+ attr :output
22
+
23
+ # This provides a read-only interface for data, which is surprisingly tricky to implement correctly.
24
+ module Reader
25
+ # rack.hijack_io must respond to:
26
+ # read, write, read_nonblock, write_nonblock, flush, close, close_read, close_write, closed?
27
+
28
+ # read behaves like IO#read. Its signature is read([length, [buffer]]). If given, length must be a non-negative Integer (>= 0) or nil, and buffer must be a String and may not be nil. If length is given and not nil, then this method reads at most length bytes from the input stream. If length is not given or nil, then this method reads all data until EOF. When EOF is reached, this method returns nil if length is given and not nil, or “” if length is not given or is nil. If buffer is given, then the read data will be placed into buffer instead of a newly created String object.
29
+ # @param length [Integer] the amount of data to read
30
+ # @param buffer [String] the buffer which will receive the data
31
+ # @return a buffer containing the data
32
+ def read(length = nil, buffer = nil)
33
+ return '' if length == 0
34
+
35
+ buffer ||= String.new.force_encoding(Encoding::BINARY)
36
+
37
+ # Take any previously buffered data and replace it into the given buffer.
38
+ if @buffer
39
+ buffer.replace(@buffer)
40
+ @buffer = nil
41
+ else
42
+ buffer.clear
43
+ end
44
+
45
+ if length
46
+ while buffer.bytesize < length and chunk = read_next
47
+ buffer << chunk
48
+ end
49
+
50
+ # This ensures the subsequent `slice!` works correctly.
51
+ buffer.force_encoding(Encoding::BINARY)
52
+
53
+ # This will be at least one copy:
54
+ @buffer = buffer.byteslice(length, buffer.bytesize)
55
+
56
+ # This should be zero-copy:
57
+ buffer.slice!(length, buffer.bytesize)
58
+
59
+ if buffer.empty?
60
+ return nil
61
+ else
62
+ return buffer
63
+ end
64
+ else
65
+ while chunk = read_next
66
+ buffer << chunk
67
+ end
68
+
69
+ return buffer
70
+ end
71
+ end
72
+
73
+ # Read at most `length` bytes from the stream. Will avoid reading from the underlying stream if possible.
74
+ def read_partial(length = nil)
75
+ if @buffer
76
+ buffer = @buffer
77
+ @buffer = nil
78
+ else
79
+ buffer = read_next
80
+ end
81
+
82
+ if buffer and length
83
+ if buffer.bytesize > length
84
+ # This ensures the subsequent `slice!` works correctly.
85
+ buffer.force_encoding(Encoding::BINARY)
86
+
87
+ @buffer = buffer.byteslice(length, buffer.bytesize)
88
+ buffer.slice!(length, buffer.bytesize)
89
+ end
90
+ end
91
+
92
+ return buffer
93
+ end
94
+
95
+ def gets
96
+ read_partial
97
+ end
98
+
99
+ def each
100
+ while chunk = read_partial
101
+ yield chunk
102
+ end
103
+ end
104
+
105
+ def read_nonblock(length, buffer = nil)
106
+ @buffer ||= read_next
107
+ chunk = nil
108
+
109
+ unless @buffer
110
+ buffer&.clear
111
+ return
112
+ end
113
+
114
+ if @buffer.bytesize > length
115
+ chunk = @buffer.byteslice(0, length)
116
+ @buffer = @buffer.byteslice(length, @buffer.bytesize)
117
+ else
118
+ chunk = @buffer
119
+ @buffer = nil
120
+ end
121
+
122
+ if buffer
123
+ buffer.replace(chunk)
124
+ else
125
+ buffer = chunk
126
+ end
127
+
128
+ return buffer
129
+ end
130
+ end
131
+
132
+ include Reader
133
+
134
+ def write(buffer)
135
+ if @output
136
+ @output.write(buffer)
137
+ return buffer.bytesize
138
+ else
139
+ raise IOError, "Stream is not writable, output has been closed!"
140
+ end
141
+ end
142
+
143
+ def write_nonblock(buffer)
144
+ write(buffer)
145
+ end
146
+
147
+ def <<(buffer)
148
+ write(buffer)
149
+ end
150
+
151
+ def flush
152
+ end
153
+
154
+ def close_read
155
+ @input&.close
156
+ @input = nil
157
+ end
158
+
159
+ # close must never be called on the input stream. huh?
160
+ def close_write
161
+ if @output.respond_to?(:close)
162
+ @output&.close
163
+ end
164
+
165
+ @output = nil
166
+ end
167
+
168
+ # Close the input and output bodies.
169
+ def close(error = nil)
170
+ self.close_read
171
+ self.close_write
172
+
173
+ return nil
174
+ ensure
175
+ @closed = true
176
+ end
177
+
178
+ # Whether the stream has been closed.
179
+ def closed?
180
+ @closed
181
+ end
182
+
183
+ # Whether there are any output chunks remaining?
184
+ def empty?
185
+ @output.empty?
186
+ end
187
+
188
+ private
189
+
190
+ def read_next
191
+ if @input
192
+ return @input.read
193
+ else
194
+ @input = nil
195
+ raise IOError, "Stream is not readable, input has been closed!"
196
+ end
197
+ end
198
+ end
199
+ 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.1.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,63 @@
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-2010, by Megan 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, 2011-2018, by Aaron Patterson.
20
+ Copyright, 2011, by Konstantin Haase.
21
+ Copyright, 2011, by Blake Mizerany.
22
+ Copyright, 2011, by Tsutomu Kuroda.
23
+ Copyright, 2012, by Jean Boussier.
24
+ Copyright, 2012, by Trevor Wennblom.
25
+ Copyright, 2012, by Anurag Priyam.
26
+ Copyright, 2012, by Hrvoje Šimić.
27
+ Copyright, 2013, by Uchio KONDO.
28
+ Copyright, 2013, by Tim Moore.
29
+ Copyright, 2013, by Postmodern.
30
+ Copyright, 2013, by Bas Vodde.
31
+ Copyright, 2013, by Joe Fiorini.
32
+ Copyright, 2014, by Wyatt Pan.
33
+ Copyright, 2014, by Lenny Marks.
34
+ Copyright, 2014, by Igor Bochkariov.
35
+ Copyright, 2014, by Max Cantor.
36
+ Copyright, 2014, by David Celis.
37
+ Copyright, 2014, by Rafael Mendonça França.
38
+ Copyright, 2014, by Jeremy Kemper.
39
+ Copyright, 2014, by Richard Schneeman.
40
+ Copyright, 2015, by Peter Wilmott.
41
+ Copyright, 2015, by Sean McGivern.
42
+ Copyright, 2015, by Tadashi Saito.
43
+ Copyright, 2015, by deepj.
44
+ Copyright, 2015, by Zachary Scott.
45
+ Copyright, 2016, by Sophie Deziel.
46
+ Copyright, 2016, by Kazuya Hotta.
47
+ Copyright, 2017, by Ryunosuke Sato.
48
+ Copyright, 2017-2023, by Samuel Williams.
49
+ Copyright, 2018, by Dillon Welch.
50
+ Copyright, 2018, by Yoshiyuki Hirano.
51
+ Copyright, 2018, by Nick LaMuro.
52
+ Copyright, 2019, by Rafael França.
53
+ Copyright, 2019, by Krzysztof Rybka.
54
+ Copyright, 2019, by Misaki Shioi.
55
+ Copyright, 2020-2022, by Jeremy Evans.
56
+ Copyright, 2021, by Katsuhiko YOSHIDA.
57
+ Copyright, 2021, by KS.
58
+ Copyright, 2021, by Stephen Paul Weber.
59
+ Copyright, 2022, by Akira Matsuda.
60
+ Copyright, 2022, by Andrew Hoglund.
5
61
 
6
62
  Permission is hereby granted, free of charge, to any person obtaining a copy
7
63
  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.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
@@ -9,36 +9,36 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2023-01-18 00:00:00.000000000 Z
12
+ date: 2023-01-27 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
29
29
  name: webrick
30
30
  requirement: !ruby/object:Gem::Requirement
31
31
  requirements:
32
- - - ">="
32
+ - - "~>"
33
33
  - !ruby/object:Gem::Version
34
- version: '0'
34
+ version: '1.8'
35
35
  type: :runtime
36
36
  prerelease: false
37
37
  version_requirements: !ruby/object:Gem::Requirement
38
38
  requirements:
39
- - - ">="
39
+ - - "~>"
40
40
  - !ruby/object:Gem::Version
41
- version: '0'
41
+ version: '1.8'
42
42
  - !ruby/object:Gem::Dependency
43
43
  name: bundler
44
44
  requirement: !ruby/object:Gem::Requirement
@@ -111,11 +111,21 @@ 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
128
+ - lib/rackup/stream.rb
119
129
  - lib/rackup/version.rb
120
130
  - license.md
121
131
  - readme.md