thin 0.7.1 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of thin might be problematic. Click here for more details.
- data/CHANGELOG +15 -0
- data/README +10 -4
- data/benchmark/abc +0 -0
- data/benchmark/runner +0 -0
- data/bin/thin +0 -0
- data/example/thin_solaris_smf.erb +36 -0
- data/example/thin_solaris_smf.readme.txt +150 -0
- data/ext/thin_parser/common.rl +3 -2
- data/ext/thin_parser/parser.c +15 -38
- data/lib/rack/adapter/loader.rb +69 -0
- data/lib/rack/adapter/rails.rb +17 -8
- data/lib/thin.rb +1 -0
- data/lib/thin/backends/base.rb +17 -0
- data/lib/thin/backends/swiftiply_client.rb +3 -2
- data/lib/thin/backends/tcp_server.rb +1 -1
- data/lib/thin/connection.rb +38 -6
- data/lib/thin/controllers/controller.rb +43 -17
- data/lib/thin/logging.rb +1 -1
- data/lib/thin/request.rb +8 -1
- data/lib/thin/runner.rb +29 -7
- data/lib/thin/server.rb +58 -28
- data/lib/thin/version.rb +3 -3
- data/lib/thin_backend.bundle +0 -0
- data/lib/thin_parser.bundle +0 -0
- data/spec/connection_spec.rb +7 -0
- data/spec/controllers/controller_spec.rb +9 -1
- data/spec/rack/loader_spec.rb +29 -0
- data/spec/{rack_rails_spec.rb → rack/rails_adapter_spec.rb} +15 -3
- data/spec/rails_app/public/dispatch.cgi +0 -0
- data/spec/rails_app/public/dispatch.fcgi +0 -0
- data/spec/rails_app/public/dispatch.rb +0 -0
- data/spec/rails_app/script/about +0 -0
- data/spec/rails_app/script/console +0 -0
- data/spec/rails_app/script/destroy +0 -0
- data/spec/rails_app/script/generate +0 -0
- data/spec/rails_app/script/performance/benchmarker +0 -0
- data/spec/rails_app/script/performance/profiler +0 -0
- data/spec/rails_app/script/performance/request +0 -0
- data/spec/rails_app/script/plugin +0 -0
- data/spec/rails_app/script/process/inspector +0 -0
- data/spec/rails_app/script/process/reaper +0 -0
- data/spec/rails_app/script/process/spawner +0 -0
- data/spec/rails_app/script/runner +0 -0
- data/spec/rails_app/script/server +0 -0
- data/spec/request/parser_spec.rb +22 -0
- data/spec/request/processing_spec.rb +9 -10
- data/spec/runner_spec.rb +14 -1
- data/spec/server/builder_spec.rb +3 -2
- data/spec/server/robustness_spec.rb +34 -0
- data/spec/server/swiftiply_spec.rb +1 -1
- data/spec/server/threaded_spec.rb +27 -0
- data/spec/server_spec.rb +69 -0
- data/spec/spec_helper.rb +10 -5
- data/tasks/gem.rake +2 -2
- data/tasks/spec.rake +24 -16
- metadata +13 -5
- data/doc/benchmarks.txt +0 -86
data/lib/rack/adapter/rails.rb
CHANGED
@@ -3,13 +3,18 @@ require 'cgi'
|
|
3
3
|
# Adapter to run a Rails app with any supported Rack handler.
|
4
4
|
# By default it will try to load the Rails application in the
|
5
5
|
# current directory in the development environment.
|
6
|
+
#
|
6
7
|
# Options:
|
7
8
|
# root: Root directory of the Rails app
|
8
|
-
#
|
9
|
+
# environment: Rails environment to run in (development [default], production or test)
|
10
|
+
# prefix: Set the relative URL root.
|
11
|
+
#
|
9
12
|
# Based on http://fuzed.rubyforge.org/ Rails adapter
|
10
13
|
module Rack
|
11
14
|
module Adapter
|
12
15
|
class Rails
|
16
|
+
FILE_METHODS = %w(GET HEAD).freeze
|
17
|
+
|
13
18
|
def initialize(options={})
|
14
19
|
@root = options[:root] || Dir.pwd
|
15
20
|
@env = options[:environment] || 'development'
|
@@ -53,16 +58,20 @@ module Rack
|
|
53
58
|
|
54
59
|
def call(env)
|
55
60
|
path = env['PATH_INFO'].chomp('/')
|
61
|
+
method = env['REQUEST_METHOD']
|
56
62
|
cached_path = (path.empty? ? 'index' : path) + ActionController::Base.page_cache_extension
|
57
63
|
|
58
|
-
if
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
64
|
+
if FILE_METHODS.include?(method)
|
65
|
+
if file_exist?(path) # Serve the file if it's there
|
66
|
+
return serve_file(env)
|
67
|
+
elsif file_exist?(cached_path) # Serve the page cache if it's there
|
68
|
+
env['PATH_INFO'] = cached_path
|
69
|
+
return serve_file(env)
|
70
|
+
end
|
65
71
|
end
|
72
|
+
|
73
|
+
# No static file, let Rails handle it
|
74
|
+
serve_rails(env)
|
66
75
|
end
|
67
76
|
|
68
77
|
protected
|
data/lib/thin.rb
CHANGED
data/lib/thin/backends/base.rb
CHANGED
@@ -4,6 +4,12 @@ module Thin
|
|
4
4
|
# * connection/disconnection to the server
|
5
5
|
# * initialization of the connections
|
6
6
|
# * manitoring of the active connections.
|
7
|
+
#
|
8
|
+
# == Implementing your own backend
|
9
|
+
# You can create your own minimal backend by inheriting this class and
|
10
|
+
# defining the +connect+ and +disconnect+ method.
|
11
|
+
# If your backend is not based on EventMachine you also need to redefine
|
12
|
+
# the +start+, +stop+, <tt>stop!</tt> and +config+ methods.
|
7
13
|
class Base
|
8
14
|
# Server serving the connections throught the backend
|
9
15
|
attr_accessor :server
|
@@ -17,6 +23,10 @@ module Thin
|
|
17
23
|
# Maximum number of connections that can be persistent
|
18
24
|
attr_accessor :maximum_persistent_connections
|
19
25
|
|
26
|
+
# Allow using threads in the backend.
|
27
|
+
attr_writer :threaded
|
28
|
+
def threaded?; @threaded end
|
29
|
+
|
20
30
|
# Number of persistent connections currently opened
|
21
31
|
attr_accessor :persistent_connection_count
|
22
32
|
|
@@ -28,6 +38,7 @@ module Thin
|
|
28
38
|
@maximum_persistent_connections = Server::DEFAULT_MAXIMUM_PERSISTENT_CONNECTIONS
|
29
39
|
end
|
30
40
|
|
41
|
+
# Start the backend and connect it.
|
31
42
|
def start
|
32
43
|
@stopping = false
|
33
44
|
|
@@ -37,6 +48,7 @@ module Thin
|
|
37
48
|
end
|
38
49
|
end
|
39
50
|
|
51
|
+
# Stop of the backend from accepting new connections.
|
40
52
|
def stop
|
41
53
|
@running = false
|
42
54
|
@stopping = true
|
@@ -46,6 +58,7 @@ module Thin
|
|
46
58
|
stop! if @connections.empty?
|
47
59
|
end
|
48
60
|
|
61
|
+
# Force stop of the backend NOW, too bad for the current connections.
|
49
62
|
def stop!
|
50
63
|
@running = false
|
51
64
|
@stopping = false
|
@@ -55,6 +68,8 @@ module Thin
|
|
55
68
|
close
|
56
69
|
end
|
57
70
|
|
71
|
+
# Configure the backend. This method will be called before droping superuser privileges,
|
72
|
+
# so you can do crazy stuff that require godlike powers here.
|
58
73
|
def config
|
59
74
|
# See http://rubyeventmachine.com/pub/rdoc/files/EPOLL.html
|
60
75
|
EventMachine.epoll
|
@@ -69,6 +84,7 @@ module Thin
|
|
69
84
|
def close
|
70
85
|
end
|
71
86
|
|
87
|
+
# Returns +true+ if the backend is connected and running.
|
72
88
|
def running?
|
73
89
|
@running
|
74
90
|
end
|
@@ -98,6 +114,7 @@ module Thin
|
|
98
114
|
connection.backend = self
|
99
115
|
connection.app = @server.app
|
100
116
|
connection.comm_inactivity_timeout = @timeout
|
117
|
+
connection.threaded = @threaded
|
101
118
|
|
102
119
|
# We control the number of persistent connections by keeping
|
103
120
|
# a count of the total one allowed yet.
|
@@ -1,14 +1,15 @@
|
|
1
1
|
module Thin
|
2
2
|
module Backends
|
3
|
+
# Backend to act as a Swiftiply client (http://swiftiply.swiftcore.org).
|
3
4
|
class SwiftiplyClient < Base
|
4
5
|
attr_accessor :key
|
5
6
|
|
6
7
|
attr_accessor :host, :port
|
7
8
|
|
8
|
-
def initialize(host, port,
|
9
|
+
def initialize(host, port, options={})
|
9
10
|
@host = host
|
10
11
|
@port = port.to_i
|
11
|
-
@key =
|
12
|
+
@key = options[:swiftiply].to_s
|
12
13
|
super()
|
13
14
|
end
|
14
15
|
|
data/lib/thin/connection.rb
CHANGED
@@ -7,7 +7,7 @@ module Thin
|
|
7
7
|
class Connection < EventMachine::Connection
|
8
8
|
include Logging
|
9
9
|
|
10
|
-
# Rack application served by this connection.
|
10
|
+
# Rack application (adapter) served by this connection.
|
11
11
|
attr_accessor :app
|
12
12
|
|
13
13
|
# Backend to the server
|
@@ -16,13 +16,19 @@ module Thin
|
|
16
16
|
# Current request served by the connection
|
17
17
|
attr_accessor :request
|
18
18
|
|
19
|
-
# Next response sent through connection
|
19
|
+
# Next response sent through the connection
|
20
20
|
attr_accessor :response
|
21
21
|
|
22
|
+
# Calling the application in a threaded allowing
|
23
|
+
# concurrent processing of requests.
|
24
|
+
attr_accessor :threaded
|
25
|
+
|
22
26
|
# Get the connection ready to process a request.
|
23
27
|
def post_init
|
24
28
|
@request = Request.new
|
25
29
|
@response = Response.new
|
30
|
+
|
31
|
+
@request.threaded = threaded
|
26
32
|
end
|
27
33
|
|
28
34
|
# Called when data is received from the client.
|
@@ -36,13 +42,31 @@ module Thin
|
|
36
42
|
end
|
37
43
|
|
38
44
|
# Called when all data was received and the request
|
39
|
-
# is ready to
|
45
|
+
# is ready to be processed.
|
40
46
|
def process
|
47
|
+
if @threaded
|
48
|
+
EventMachine.defer(method(:pre_process), method(:post_process))
|
49
|
+
else
|
50
|
+
post_process(pre_process)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def pre_process
|
41
55
|
# Add client info to the request env
|
42
56
|
@request.remote_address = remote_address
|
43
57
|
|
44
|
-
# Process the request
|
45
|
-
@
|
58
|
+
# Process the request calling the Rack adapter
|
59
|
+
@app.call(@request.env)
|
60
|
+
rescue
|
61
|
+
handle_error
|
62
|
+
terminate_request
|
63
|
+
nil # Signal to post_process that the request could not be processed
|
64
|
+
end
|
65
|
+
|
66
|
+
def post_process(result)
|
67
|
+
return unless result
|
68
|
+
|
69
|
+
@response.status, @response.headers, @response.body = result
|
46
70
|
|
47
71
|
# Make the response persistent if requested by the client
|
48
72
|
@response.persistent! if @request.persistent?
|
@@ -57,10 +81,18 @@ module Thin
|
|
57
81
|
close_connection_after_writing unless persistent?
|
58
82
|
|
59
83
|
rescue
|
84
|
+
handle_error
|
85
|
+
ensure
|
86
|
+
terminate_request
|
87
|
+
end
|
88
|
+
|
89
|
+
def handle_error
|
60
90
|
log "!! Unexpected error while processing request: #{$!.message}"
|
61
91
|
log_error
|
62
92
|
close_connection rescue nil
|
63
|
-
|
93
|
+
end
|
94
|
+
|
95
|
+
def terminate_request
|
64
96
|
@request.close rescue nil
|
65
97
|
@response.close rescue nil
|
66
98
|
|
@@ -1,15 +1,23 @@
|
|
1
1
|
require 'yaml'
|
2
2
|
|
3
3
|
module Thin
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
4
|
+
# Error raised that will abort the process and print not backtrace.
|
5
|
+
class RunnerError < RuntimeError; end
|
6
|
+
|
7
|
+
# Raised when a mandatory option is missing to run a command.
|
8
|
+
class OptionRequired < RunnerError
|
9
|
+
def initialize(option)
|
10
|
+
super("#{option} option required")
|
10
11
|
end
|
12
|
+
end
|
11
13
|
|
12
|
-
|
14
|
+
# Raised when an option is not valid.
|
15
|
+
class InvalidOption < RunnerError; end
|
16
|
+
|
17
|
+
# Build and control Thin servers.
|
18
|
+
# Hey Controller pattern is not only for web apps yo!
|
19
|
+
module Controllers
|
20
|
+
# Controls one Thin server.
|
13
21
|
# Allow to start, stop, restart and configure a single thin server.
|
14
22
|
class Controller
|
15
23
|
include Logging
|
@@ -27,35 +35,38 @@ module Thin
|
|
27
35
|
end
|
28
36
|
|
29
37
|
def start
|
38
|
+
# Select proper backend
|
30
39
|
server = case
|
40
|
+
when @options.has_key?(:backend)
|
41
|
+
Server.new(@options[:address], @options[:port], :backend => eval(@options[:backend], TOPLEVEL_BINDING))
|
31
42
|
when @options.has_key?(:socket)
|
32
43
|
Server.new(@options[:socket])
|
33
|
-
when @options.has_key?(:swiftiply)
|
34
|
-
Server.new(Backends::SwiftiplyClient.new(@options[:address], @options[:port], @options[:swiftiply]))
|
35
44
|
else
|
36
45
|
Server.new(@options[:address], @options[:port])
|
37
46
|
end
|
38
|
-
|
47
|
+
|
48
|
+
# Set options
|
39
49
|
server.pid_file = @options[:pid]
|
40
50
|
server.log_file = @options[:log]
|
41
51
|
server.timeout = @options[:timeout]
|
42
52
|
server.maximum_connections = @options[:max_conns]
|
43
53
|
server.maximum_persistent_connections = @options[:max_persistent_conns]
|
54
|
+
server.threaded = @options[:threaded]
|
44
55
|
|
56
|
+
# Detach the process, after this line the current process returns
|
45
57
|
server.daemonize if @options[:daemonize]
|
46
58
|
|
47
|
-
|
59
|
+
# +config+ must be called before changing privileges since it might require superuser power.
|
60
|
+
server.config
|
48
61
|
|
49
62
|
server.change_privilege @options[:user], @options[:group] if @options[:user] && @options[:group]
|
50
63
|
|
51
64
|
# If a Rack config file is specified we eval it inside a Rack::Builder block to create
|
52
|
-
# a Rack adapter from it.
|
53
|
-
# to Rails adapter.
|
65
|
+
# a Rack adapter from it. Or else we guess which adapter to use and load it.
|
54
66
|
if @options[:rackup]
|
55
|
-
|
56
|
-
server.app = eval("Rack::Builder.new {( #{rackup_code}\n )}.to_app", TOPLEVEL_BINDING, @options[:rackup])
|
67
|
+
server.app = load_rackup_config
|
57
68
|
else
|
58
|
-
server.app =
|
69
|
+
server.app = load_adapter
|
59
70
|
end
|
60
71
|
|
61
72
|
# If a prefix is required, wrap in Rack URL mapper
|
@@ -64,7 +75,8 @@ module Thin
|
|
64
75
|
# If a stats URL is specified, wrap in Stats adapter
|
65
76
|
server.app = Stats::Adapter.new(server.app, @options[:stats]) if @options[:stats]
|
66
77
|
|
67
|
-
# Register restart procedure
|
78
|
+
# Register restart procedure which just start another process with same options,
|
79
|
+
# so that's why this is done here.
|
68
80
|
server.on_restart { Command.run(:start, @options) }
|
69
81
|
|
70
82
|
server.start
|
@@ -141,6 +153,20 @@ module Thin
|
|
141
153
|
sleep 1 if File.exist?(file) # HACK Give the thread a little time to open the file
|
142
154
|
tail_thread
|
143
155
|
end
|
156
|
+
|
157
|
+
private
|
158
|
+
def load_adapter
|
159
|
+
adapter = @options[:adapter] || Rack::Adapter.guess(@options[:chdir])
|
160
|
+
log ">> Using #{adapter} adapter"
|
161
|
+
Rack::Adapter.for(adapter, @options)
|
162
|
+
rescue Rack::AdapterNotFound => e
|
163
|
+
raise InvalidOption, e.message
|
164
|
+
end
|
165
|
+
|
166
|
+
def load_rackup_config
|
167
|
+
rackup_code = File.read(@options[:rackup])
|
168
|
+
eval("Rack::Builder.new {( #{rackup_code}\n )}.to_app", TOPLEVEL_BINDING, @options[:rackup])
|
169
|
+
end
|
144
170
|
end
|
145
171
|
end
|
146
172
|
end
|
data/lib/thin/logging.rb
CHANGED
@@ -15,7 +15,7 @@ module Thin
|
|
15
15
|
def silent?; @silent end
|
16
16
|
end
|
17
17
|
|
18
|
-
# Deprecated silencer methods, those are now
|
18
|
+
# Deprecated silencer methods, those are now module methods
|
19
19
|
def silent
|
20
20
|
warn "`#{self.class.name}\#silent` deprecated, use `Thin::Logging.silent?` instead"
|
21
21
|
Logging.silent?
|
data/lib/thin/request.rb
CHANGED
@@ -12,6 +12,7 @@ module Thin
|
|
12
12
|
# and into a tempfile for reading.
|
13
13
|
MAX_BODY = 1024 * (80 + 32)
|
14
14
|
BODY_TMPFILE = 'thin-body'.freeze
|
15
|
+
MAX_HEADER = 1024 * (80 + 32)
|
15
16
|
|
16
17
|
# Freeze some HTTP header names & values
|
17
18
|
SERVER_SOFTWARE = 'SERVER_SOFTWARE'.freeze
|
@@ -60,7 +61,7 @@ module Thin
|
|
60
61
|
RACK_RUN_ONCE => false
|
61
62
|
}
|
62
63
|
end
|
63
|
-
|
64
|
+
|
64
65
|
# Parse a chunk of data into the request environment
|
65
66
|
# Raises a +InvalidRequest+ if invalid.
|
66
67
|
# Returns +true+ if the parsing is complete.
|
@@ -69,6 +70,8 @@ module Thin
|
|
69
70
|
body << data
|
70
71
|
else # Parse more header using the super parser
|
71
72
|
@data << data
|
73
|
+
raise InvalidRequest, 'Header longer than allowed' if @data.size > MAX_HEADER
|
74
|
+
|
72
75
|
@nparsed = @parser.execute(@env, @data, @nparsed)
|
73
76
|
|
74
77
|
# Transfert to a tempfile if body is very big
|
@@ -119,6 +122,10 @@ module Thin
|
|
119
122
|
@env[FORWARDED_FOR]
|
120
123
|
end
|
121
124
|
|
125
|
+
def threaded=(value)
|
126
|
+
@env[RACK_MULTITHREAD] = value
|
127
|
+
end
|
128
|
+
|
122
129
|
# Close any resource used by the request
|
123
130
|
def close
|
124
131
|
@body.delete if @body.class == Tempfile
|
data/lib/thin/runner.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'optparse'
|
2
2
|
require 'yaml'
|
3
3
|
|
4
|
-
module Thin
|
4
|
+
module Thin
|
5
5
|
# CLI runner.
|
6
6
|
# Parse options and send command to the correct Controller.
|
7
7
|
class Runner
|
@@ -62,14 +62,19 @@ module Thin
|
|
62
62
|
opts.on("-p", "--port PORT", "use PORT (default: #{@options[:port]})") { |port| @options[:port] = port.to_i }
|
63
63
|
opts.on("-S", "--socket FILE", "bind to unix domain socket") { |file| @options[:socket] = file }
|
64
64
|
opts.on("-y", "--swiftiply [KEY]", "Run using swiftiply") { |key| @options[:swiftiply] = key }
|
65
|
-
opts.on("-
|
66
|
-
|
65
|
+
opts.on("-A", "--adapter NAME", "Rack adapter to use (default: autodetect)",
|
66
|
+
"(#{Rack::ADAPTERS.keys.join(', ')})") { |name| @options[:adapter] = name }
|
67
|
+
opts.on("-R", "--rackup FILE", "Load a Rack config file instead of " +
|
68
|
+
"Rack adapter") { |file| @options[:rackup] = file }
|
67
69
|
opts.on("-c", "--chdir DIR", "Change to dir before starting") { |dir| @options[:chdir] = File.expand_path(dir) }
|
68
|
-
opts.on("-r", "--rackup FILE", "Load a Rack config file instead of " +
|
69
|
-
"Rails adapter") { |file| @options[:rackup] = file }
|
70
|
-
opts.on( "--prefix PATH", "Mount the app under PATH (start with /)") { |path| @options[:prefix] = path }
|
71
70
|
opts.on( "--stats PATH", "Mount the Stats adapter under PATH") { |path| @options[:stats] = path }
|
72
71
|
|
72
|
+
opts.separator ""
|
73
|
+
opts.separator "Adapter options:"
|
74
|
+
opts.on("-e", "--environment ENV", "Framework environment " +
|
75
|
+
"(default: #{@options[:environment]})") { |env| @options[:environment] = env }
|
76
|
+
opts.on( "--prefix PATH", "Mount the app under PATH (start with /)") { |path| @options[:prefix] = path }
|
77
|
+
|
73
78
|
unless Thin.win? # Daemonizing not supported on Windows
|
74
79
|
opts.separator ""
|
75
80
|
opts.separator "Daemon options:"
|
@@ -94,6 +99,7 @@ module Thin
|
|
94
99
|
opts.separator ""
|
95
100
|
opts.separator "Tuning options:"
|
96
101
|
|
102
|
+
opts.on("-b", "--backend CLASS", "Backend to use, full classname") { |name| @options[:backend] = name }
|
97
103
|
opts.on("-t", "--timeout SEC", "Request or command timeout in sec " +
|
98
104
|
"(default: #{@options[:timeout]})") { |sec| @options[:timeout] = sec.to_i }
|
99
105
|
opts.on( "--max-conns NUM", "Maximum number of connections " +
|
@@ -102,10 +108,13 @@ module Thin
|
|
102
108
|
opts.on( "--max-persistent-conns NUM",
|
103
109
|
"Maximum number of persistent connections",
|
104
110
|
"(default: #{@options[:max_persistent_conns]})") { |num| @options[:max_persistent_conns] = num.to_i }
|
111
|
+
opts.on( "--threaded", "Call the Rack application in threads " +
|
112
|
+
"[experimental]") { @options[:threaded] = true }
|
105
113
|
|
106
114
|
opts.separator ""
|
107
115
|
opts.separator "Common options:"
|
108
116
|
|
117
|
+
opts.on_tail("-r", "--require FILE", "require the library") { |file| ruby_require file }
|
109
118
|
opts.on_tail("-D", "--debug", "Set debbuging on") { Logging.debug = true }
|
110
119
|
opts.on_tail("-V", "--trace", "Set tracing on (log raw request/response)") { Logging.trace = true }
|
111
120
|
opts.on_tail("-h", "--help", "Show this message") { puts opts; exit }
|
@@ -153,7 +162,11 @@ module Thin
|
|
153
162
|
end
|
154
163
|
|
155
164
|
if controller.respond_to?(@command)
|
156
|
-
|
165
|
+
begin
|
166
|
+
controller.send(@command, *@arguments)
|
167
|
+
rescue RunnerError => e
|
168
|
+
abort e.message
|
169
|
+
end
|
157
170
|
else
|
158
171
|
abort "Invalid options for command: #{@command}"
|
159
172
|
end
|
@@ -175,5 +188,14 @@ module Thin
|
|
175
188
|
YAML.load_file(file).each { |key, value| @options[key.to_sym] = value }
|
176
189
|
end
|
177
190
|
end
|
191
|
+
|
192
|
+
def ruby_require(file)
|
193
|
+
if File.extname(file) == '.ru'
|
194
|
+
warn 'WARNING: Use the -R option to load a Rack config file'
|
195
|
+
@options[:rackup] = file
|
196
|
+
else
|
197
|
+
require file
|
198
|
+
end
|
199
|
+
end
|
178
200
|
end
|
179
201
|
end
|