puma-simon 3.7.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/issue_template.md +20 -0
- data/.gitignore +18 -0
- data/.hoeignore +12 -0
- data/.travis.yml +29 -0
- data/DEPLOYMENT.md +91 -0
- data/Gemfile +12 -0
- data/History.md +1254 -0
- data/LICENSE +26 -0
- data/Manifest.txt +78 -0
- data/README.md +353 -0
- data/Rakefile +158 -0
- data/Release.md +9 -0
- data/bin/puma +10 -0
- data/bin/puma-wild +31 -0
- data/bin/pumactl +12 -0
- data/docs/nginx.md +80 -0
- data/docs/signals.md +43 -0
- data/docs/systemd.md +197 -0
- data/examples/CA/cacert.pem +23 -0
- data/examples/CA/newcerts/cert_1.pem +19 -0
- data/examples/CA/newcerts/cert_2.pem +19 -0
- data/examples/CA/private/cakeypair.pem +30 -0
- data/examples/CA/serial +1 -0
- data/examples/config.rb +200 -0
- data/examples/plugins/redis_stop_puma.rb +46 -0
- data/examples/puma/cert_puma.pem +19 -0
- data/examples/puma/client-certs/ca.crt +19 -0
- data/examples/puma/client-certs/ca.key +27 -0
- data/examples/puma/client-certs/client.crt +19 -0
- data/examples/puma/client-certs/client.key +27 -0
- data/examples/puma/client-certs/client_expired.crt +19 -0
- data/examples/puma/client-certs/client_expired.key +27 -0
- data/examples/puma/client-certs/client_unknown.crt +19 -0
- data/examples/puma/client-certs/client_unknown.key +27 -0
- data/examples/puma/client-certs/generate.rb +78 -0
- data/examples/puma/client-certs/keystore.jks +0 -0
- data/examples/puma/client-certs/server.crt +19 -0
- data/examples/puma/client-certs/server.key +27 -0
- data/examples/puma/client-certs/server.p12 +0 -0
- data/examples/puma/client-certs/unknown_ca.crt +19 -0
- data/examples/puma/client-certs/unknown_ca.key +27 -0
- data/examples/puma/csr_puma.pem +11 -0
- data/examples/puma/keystore.jks +0 -0
- data/examples/puma/puma_keypair.pem +15 -0
- data/examples/qc_config.rb +13 -0
- data/ext/puma_http11/PumaHttp11Service.java +17 -0
- data/ext/puma_http11/ext_help.h +15 -0
- data/ext/puma_http11/extconf.rb +15 -0
- data/ext/puma_http11/http11_parser.c +1069 -0
- data/ext/puma_http11/http11_parser.h +65 -0
- data/ext/puma_http11/http11_parser.java.rl +161 -0
- data/ext/puma_http11/http11_parser.rl +147 -0
- data/ext/puma_http11/http11_parser_common.rl +54 -0
- data/ext/puma_http11/io_buffer.c +155 -0
- data/ext/puma_http11/mini_ssl.c +457 -0
- data/ext/puma_http11/org/jruby/puma/Http11.java +234 -0
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +473 -0
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +339 -0
- data/ext/puma_http11/puma_http11.c +500 -0
- data/gemfiles/2.1-Gemfile +12 -0
- data/lib/puma.rb +15 -0
- data/lib/puma/accept_nonblock.rb +23 -0
- data/lib/puma/app/status.rb +66 -0
- data/lib/puma/binder.rb +402 -0
- data/lib/puma/cli.rb +220 -0
- data/lib/puma/client.rb +434 -0
- data/lib/puma/cluster.rb +510 -0
- data/lib/puma/commonlogger.rb +106 -0
- data/lib/puma/compat.rb +14 -0
- data/lib/puma/configuration.rb +364 -0
- data/lib/puma/const.rb +224 -0
- data/lib/puma/control_cli.rb +259 -0
- data/lib/puma/convenient.rb +23 -0
- data/lib/puma/daemon_ext.rb +31 -0
- data/lib/puma/delegation.rb +11 -0
- data/lib/puma/detect.rb +13 -0
- data/lib/puma/dsl.rb +486 -0
- data/lib/puma/events.rb +152 -0
- data/lib/puma/io_buffer.rb +7 -0
- data/lib/puma/java_io_buffer.rb +45 -0
- data/lib/puma/jruby_restart.rb +83 -0
- data/lib/puma/launcher.rb +410 -0
- data/lib/puma/minissl.rb +221 -0
- data/lib/puma/null_io.rb +42 -0
- data/lib/puma/plugin.rb +115 -0
- data/lib/puma/plugin/tmp_restart.rb +35 -0
- data/lib/puma/rack/backports/uri/common_193.rb +33 -0
- data/lib/puma/rack/builder.rb +298 -0
- data/lib/puma/rack/urlmap.rb +91 -0
- data/lib/puma/rack_default.rb +7 -0
- data/lib/puma/reactor.rb +210 -0
- data/lib/puma/runner.rb +171 -0
- data/lib/puma/server.rb +949 -0
- data/lib/puma/single.rb +112 -0
- data/lib/puma/state_file.rb +29 -0
- data/lib/puma/tcp_logger.rb +39 -0
- data/lib/puma/thread_pool.rb +297 -0
- data/lib/puma/util.rb +128 -0
- data/lib/rack/handler/puma.rb +78 -0
- data/puma.gemspec +52 -0
- data/test/ab_rs.rb +22 -0
- data/test/config.rb +2 -0
- data/test/config/app.rb +9 -0
- data/test/config/plugin.rb +1 -0
- data/test/config/settings.rb +2 -0
- data/test/config/state_file_testing_config.rb +14 -0
- data/test/hello-bind.ru +2 -0
- data/test/hello-delay.ru +3 -0
- data/test/hello-map.ru +3 -0
- data/test/hello-post.ru +4 -0
- data/test/hello-stuck.ru +1 -0
- data/test/hello-tcp.ru +5 -0
- data/test/hello.ru +1 -0
- data/test/hijack.ru +6 -0
- data/test/hijack2.ru +5 -0
- data/test/lobster.ru +4 -0
- data/test/shell/run.sh +24 -0
- data/test/shell/t1.rb +19 -0
- data/test/shell/t1_conf.rb +3 -0
- data/test/shell/t2.rb +17 -0
- data/test/shell/t2_conf.rb +6 -0
- data/test/shell/t3.rb +25 -0
- data/test/shell/t3_conf.rb +5 -0
- data/test/slow.ru +4 -0
- data/test/ssl_config.rb +4 -0
- data/test/test_app_status.rb +93 -0
- data/test/test_binder.rb +31 -0
- data/test/test_cli.rb +209 -0
- data/test/test_config.rb +95 -0
- data/test/test_events.rb +161 -0
- data/test/test_helper.rb +50 -0
- data/test/test_http10.rb +27 -0
- data/test/test_http11.rb +186 -0
- data/test/test_integration.rb +247 -0
- data/test/test_iobuffer.rb +39 -0
- data/test/test_minissl.rb +29 -0
- data/test/test_null_io.rb +49 -0
- data/test/test_persistent.rb +245 -0
- data/test/test_puma_server.rb +626 -0
- data/test/test_puma_server_ssl.rb +222 -0
- data/test/test_rack_handler.rb +57 -0
- data/test/test_rack_server.rb +138 -0
- data/test/test_tcp_logger.rb +39 -0
- data/test/test_tcp_rack.rb +36 -0
- data/test/test_thread_pool.rb +250 -0
- data/test/test_unix_socket.rb +35 -0
- data/test/test_web_server.rb +88 -0
- data/tools/jungle/README.md +9 -0
- data/tools/jungle/init.d/README.md +59 -0
- data/tools/jungle/init.d/puma +421 -0
- data/tools/jungle/init.d/run-puma +18 -0
- data/tools/jungle/upstart/README.md +61 -0
- data/tools/jungle/upstart/puma-manager.conf +31 -0
- data/tools/jungle/upstart/puma.conf +69 -0
- data/tools/trickletest.rb +45 -0
- metadata +297 -0
@@ -0,0 +1,298 @@
|
|
1
|
+
module Puma
|
2
|
+
end
|
3
|
+
|
4
|
+
module Puma::Rack
|
5
|
+
class Options
|
6
|
+
def parse!(args)
|
7
|
+
options = {}
|
8
|
+
opt_parser = OptionParser.new("", 24, ' ') do |opts|
|
9
|
+
opts.banner = "Usage: rackup [ruby options] [rack options] [rackup config]"
|
10
|
+
|
11
|
+
opts.separator ""
|
12
|
+
opts.separator "Ruby options:"
|
13
|
+
|
14
|
+
lineno = 1
|
15
|
+
opts.on("-e", "--eval LINE", "evaluate a LINE of code") { |line|
|
16
|
+
eval line, TOPLEVEL_BINDING, "-e", lineno
|
17
|
+
lineno += 1
|
18
|
+
}
|
19
|
+
|
20
|
+
opts.on("-b", "--builder BUILDER_LINE", "evaluate a BUILDER_LINE of code as a builder script") { |line|
|
21
|
+
options[:builder] = line
|
22
|
+
}
|
23
|
+
|
24
|
+
opts.on("-d", "--debug", "set debugging flags (set $DEBUG to true)") {
|
25
|
+
options[:debug] = true
|
26
|
+
}
|
27
|
+
opts.on("-w", "--warn", "turn warnings on for your script") {
|
28
|
+
options[:warn] = true
|
29
|
+
}
|
30
|
+
opts.on("-q", "--quiet", "turn off logging") {
|
31
|
+
options[:quiet] = true
|
32
|
+
}
|
33
|
+
|
34
|
+
opts.on("-I", "--include PATH",
|
35
|
+
"specify $LOAD_PATH (may be used more than once)") { |path|
|
36
|
+
(options[:include] ||= []).concat(path.split(":"))
|
37
|
+
}
|
38
|
+
|
39
|
+
opts.on("-r", "--require LIBRARY",
|
40
|
+
"require the library, before executing your script") { |library|
|
41
|
+
options[:require] = library
|
42
|
+
}
|
43
|
+
|
44
|
+
opts.separator ""
|
45
|
+
opts.separator "Rack options:"
|
46
|
+
opts.on("-s", "--server SERVER", "serve using SERVER (thin/puma/webrick/mongrel)") { |s|
|
47
|
+
options[:server] = s
|
48
|
+
}
|
49
|
+
|
50
|
+
opts.on("-o", "--host HOST", "listen on HOST (default: localhost)") { |host|
|
51
|
+
options[:Host] = host
|
52
|
+
}
|
53
|
+
|
54
|
+
opts.on("-p", "--port PORT", "use PORT (default: 9292)") { |port|
|
55
|
+
options[:Port] = port
|
56
|
+
}
|
57
|
+
|
58
|
+
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|
|
59
|
+
name, value = name.split('=', 2)
|
60
|
+
value = true if value.nil?
|
61
|
+
options[name.to_sym] = value
|
62
|
+
}
|
63
|
+
|
64
|
+
opts.on("-E", "--env ENVIRONMENT", "use ENVIRONMENT for defaults (default: development)") { |e|
|
65
|
+
options[:environment] = e
|
66
|
+
}
|
67
|
+
|
68
|
+
opts.on("-D", "--daemonize", "run daemonized in the background") { |d|
|
69
|
+
options[:daemonize] = d ? true : false
|
70
|
+
}
|
71
|
+
|
72
|
+
opts.on("-P", "--pid FILE", "file to store PID") { |f|
|
73
|
+
options[:pid] = ::File.expand_path(f)
|
74
|
+
}
|
75
|
+
|
76
|
+
opts.separator ""
|
77
|
+
opts.separator "Common options:"
|
78
|
+
|
79
|
+
opts.on_tail("-h", "-?", "--help", "Show this message") do
|
80
|
+
puts opts
|
81
|
+
puts handler_opts(options)
|
82
|
+
|
83
|
+
exit
|
84
|
+
end
|
85
|
+
|
86
|
+
opts.on_tail("--version", "Show version") do
|
87
|
+
puts "Rack #{Rack.version} (Release: #{Rack.release})"
|
88
|
+
exit
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
begin
|
93
|
+
opt_parser.parse! args
|
94
|
+
rescue OptionParser::InvalidOption => e
|
95
|
+
warn e.message
|
96
|
+
abort opt_parser.to_s
|
97
|
+
end
|
98
|
+
|
99
|
+
options[:config] = args.last if args.last
|
100
|
+
options
|
101
|
+
end
|
102
|
+
|
103
|
+
def handler_opts(options)
|
104
|
+
begin
|
105
|
+
info = []
|
106
|
+
server = Rack::Handler.get(options[:server]) || Rack::Handler.default(options)
|
107
|
+
if server && server.respond_to?(:valid_options)
|
108
|
+
info << ""
|
109
|
+
info << "Server-specific options for #{server.name}:"
|
110
|
+
|
111
|
+
has_options = false
|
112
|
+
server.valid_options.each do |name, description|
|
113
|
+
next if name.to_s.match(/^(Host|Port)[^a-zA-Z]/) # ignore handler's host and port options, we do our own.
|
114
|
+
info << " -O %-21s %s" % [name, description]
|
115
|
+
has_options = true
|
116
|
+
end
|
117
|
+
return "" if !has_options
|
118
|
+
end
|
119
|
+
info.join("\n")
|
120
|
+
rescue NameError
|
121
|
+
return "Warning: Could not find handler specified (#{options[:server] || 'default'}) to determine handler-specific options"
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# Rack::Builder implements a small DSL to iteratively construct Rack
|
127
|
+
# applications.
|
128
|
+
#
|
129
|
+
# Example:
|
130
|
+
#
|
131
|
+
# require 'rack/lobster'
|
132
|
+
# app = Rack::Builder.new do
|
133
|
+
# use Rack::CommonLogger
|
134
|
+
# use Rack::ShowExceptions
|
135
|
+
# map "/lobster" do
|
136
|
+
# use Rack::Lint
|
137
|
+
# run Rack::Lobster.new
|
138
|
+
# end
|
139
|
+
# end
|
140
|
+
#
|
141
|
+
# run app
|
142
|
+
#
|
143
|
+
# Or
|
144
|
+
#
|
145
|
+
# app = Rack::Builder.app do
|
146
|
+
# use Rack::CommonLogger
|
147
|
+
# run lambda { |env| [200, {'Content-Type' => 'text/plain'}, ['OK']] }
|
148
|
+
# end
|
149
|
+
#
|
150
|
+
# run app
|
151
|
+
#
|
152
|
+
# +use+ adds middleware to the stack, +run+ dispatches to an application.
|
153
|
+
# You can use +map+ to construct a Rack::URLMap in a convenient way.
|
154
|
+
|
155
|
+
class Builder
|
156
|
+
def self.parse_file(config, opts = Options.new)
|
157
|
+
options = {}
|
158
|
+
if config =~ /\.ru$/
|
159
|
+
cfgfile = ::File.read(config)
|
160
|
+
if cfgfile[/^#\\(.*)/] && opts
|
161
|
+
options = opts.parse! $1.split(/\s+/)
|
162
|
+
end
|
163
|
+
cfgfile.sub!(/^__END__\n.*\Z/m, '')
|
164
|
+
app = new_from_string cfgfile, config
|
165
|
+
else
|
166
|
+
require config
|
167
|
+
app = Object.const_get(::File.basename(config, '.rb').capitalize)
|
168
|
+
end
|
169
|
+
return app, options
|
170
|
+
end
|
171
|
+
|
172
|
+
def self.new_from_string(builder_script, file="(rackup)")
|
173
|
+
eval "Puma::Rack::Builder.new {\n" + builder_script + "\n}.to_app",
|
174
|
+
TOPLEVEL_BINDING, file, 0
|
175
|
+
end
|
176
|
+
|
177
|
+
def initialize(default_app = nil,&block)
|
178
|
+
@use, @map, @run, @warmup = [], nil, default_app, nil
|
179
|
+
|
180
|
+
# Conditionally load rack now, so that any rack middlewares,
|
181
|
+
# etc are available.
|
182
|
+
begin
|
183
|
+
require 'rack'
|
184
|
+
rescue LoadError
|
185
|
+
end
|
186
|
+
|
187
|
+
instance_eval(&block) if block_given?
|
188
|
+
end
|
189
|
+
|
190
|
+
def self.app(default_app = nil, &block)
|
191
|
+
self.new(default_app, &block).to_app
|
192
|
+
end
|
193
|
+
|
194
|
+
# Specifies middleware to use in a stack.
|
195
|
+
#
|
196
|
+
# class Middleware
|
197
|
+
# def initialize(app)
|
198
|
+
# @app = app
|
199
|
+
# end
|
200
|
+
#
|
201
|
+
# def call(env)
|
202
|
+
# env["rack.some_header"] = "setting an example"
|
203
|
+
# @app.call(env)
|
204
|
+
# end
|
205
|
+
# end
|
206
|
+
#
|
207
|
+
# use Middleware
|
208
|
+
# run lambda { |env| [200, { "Content-Type" => "text/plain" }, ["OK"]] }
|
209
|
+
#
|
210
|
+
# All requests through to this application will first be processed by the middleware class.
|
211
|
+
# The +call+ method in this example sets an additional environment key which then can be
|
212
|
+
# referenced in the application if required.
|
213
|
+
def use(middleware, *args, &block)
|
214
|
+
if @map
|
215
|
+
mapping, @map = @map, nil
|
216
|
+
@use << proc { |app| generate_map app, mapping }
|
217
|
+
end
|
218
|
+
@use << proc { |app| middleware.new(app, *args, &block) }
|
219
|
+
end
|
220
|
+
|
221
|
+
# Takes an argument that is an object that responds to #call and returns a Rack response.
|
222
|
+
# The simplest form of this is a lambda object:
|
223
|
+
#
|
224
|
+
# run lambda { |env| [200, { "Content-Type" => "text/plain" }, ["OK"]] }
|
225
|
+
#
|
226
|
+
# However this could also be a class:
|
227
|
+
#
|
228
|
+
# class Heartbeat
|
229
|
+
# def self.call(env)
|
230
|
+
# [200, { "Content-Type" => "text/plain" }, ["OK"]]
|
231
|
+
# end
|
232
|
+
# end
|
233
|
+
#
|
234
|
+
# run Heartbeat
|
235
|
+
def run(app)
|
236
|
+
@run = app
|
237
|
+
end
|
238
|
+
|
239
|
+
# Takes a lambda or block that is used to warm-up the application.
|
240
|
+
#
|
241
|
+
# warmup do |app|
|
242
|
+
# client = Rack::MockRequest.new(app)
|
243
|
+
# client.get('/')
|
244
|
+
# end
|
245
|
+
#
|
246
|
+
# use SomeMiddleware
|
247
|
+
# run MyApp
|
248
|
+
def warmup(prc=nil, &block)
|
249
|
+
@warmup = prc || block
|
250
|
+
end
|
251
|
+
|
252
|
+
# Creates a route within the application.
|
253
|
+
#
|
254
|
+
# Rack::Builder.app do
|
255
|
+
# map '/' do
|
256
|
+
# run Heartbeat
|
257
|
+
# end
|
258
|
+
# end
|
259
|
+
#
|
260
|
+
# The +use+ method can also be used here to specify middleware to run under a specific path:
|
261
|
+
#
|
262
|
+
# Rack::Builder.app do
|
263
|
+
# map '/' do
|
264
|
+
# use Middleware
|
265
|
+
# run Heartbeat
|
266
|
+
# end
|
267
|
+
# end
|
268
|
+
#
|
269
|
+
# This example includes a piece of middleware which will run before requests hit +Heartbeat+.
|
270
|
+
#
|
271
|
+
def map(path, &block)
|
272
|
+
@map ||= {}
|
273
|
+
@map[path] = block
|
274
|
+
end
|
275
|
+
|
276
|
+
def to_app
|
277
|
+
app = @map ? generate_map(@run, @map) : @run
|
278
|
+
fail "missing run or map statement" unless app
|
279
|
+
app = @use.reverse.inject(app) { |a,e| e[a] }
|
280
|
+
@warmup.call(app) if @warmup
|
281
|
+
app
|
282
|
+
end
|
283
|
+
|
284
|
+
def call(env)
|
285
|
+
to_app.call(env)
|
286
|
+
end
|
287
|
+
|
288
|
+
private
|
289
|
+
|
290
|
+
def generate_map(default_app, mapping)
|
291
|
+
require 'puma/rack/urlmap'
|
292
|
+
|
293
|
+
mapped = default_app ? {'/' => default_app} : {}
|
294
|
+
mapping.each { |r,b| mapped[r] = self.class.new(default_app, &b).to_app }
|
295
|
+
URLMap.new(mapped)
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
module Puma::Rack
|
2
|
+
# Rack::URLMap takes a hash mapping urls or paths to apps, and
|
3
|
+
# dispatches accordingly. Support for HTTP/1.1 host names exists if
|
4
|
+
# the URLs start with <tt>http://</tt> or <tt>https://</tt>.
|
5
|
+
#
|
6
|
+
# URLMap modifies the SCRIPT_NAME and PATH_INFO such that the part
|
7
|
+
# relevant for dispatch is in the SCRIPT_NAME, and the rest in the
|
8
|
+
# PATH_INFO. This should be taken care of when you need to
|
9
|
+
# reconstruct the URL in order to create links.
|
10
|
+
#
|
11
|
+
# URLMap dispatches in such a way that the longest paths are tried
|
12
|
+
# first, since they are most specific.
|
13
|
+
|
14
|
+
class URLMap
|
15
|
+
NEGATIVE_INFINITY = -1.0 / 0.0
|
16
|
+
INFINITY = 1.0 / 0.0
|
17
|
+
|
18
|
+
def initialize(map = {})
|
19
|
+
remap(map)
|
20
|
+
end
|
21
|
+
|
22
|
+
def remap(map)
|
23
|
+
@mapping = map.map { |location, app|
|
24
|
+
if location =~ %r{\Ahttps?://(.*?)(/.*)}
|
25
|
+
host, location = $1, $2
|
26
|
+
else
|
27
|
+
host = nil
|
28
|
+
end
|
29
|
+
|
30
|
+
unless location[0] == ?/
|
31
|
+
raise ArgumentError, "paths need to start with /"
|
32
|
+
end
|
33
|
+
|
34
|
+
location = location.chomp('/')
|
35
|
+
match = Regexp.new("^#{Regexp.quote(location).gsub('/', '/+')}(.*)", nil, 'n')
|
36
|
+
|
37
|
+
[host, location, match, app]
|
38
|
+
}.sort_by do |(host, location, _, _)|
|
39
|
+
[host ? -host.size : INFINITY, -location.size]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def call(env)
|
44
|
+
path = env['PATH_INFO']
|
45
|
+
script_name = env['SCRIPT_NAME']
|
46
|
+
http_host = env['HTTP_HOST']
|
47
|
+
server_name = env['SERVER_NAME']
|
48
|
+
server_port = env['SERVER_PORT']
|
49
|
+
|
50
|
+
is_same_server = casecmp?(http_host, server_name) ||
|
51
|
+
casecmp?(http_host, "#{server_name}:#{server_port}")
|
52
|
+
|
53
|
+
@mapping.each do |host, location, match, app|
|
54
|
+
unless casecmp?(http_host, host) \
|
55
|
+
|| casecmp?(server_name, host) \
|
56
|
+
|| (!host && is_same_server)
|
57
|
+
next
|
58
|
+
end
|
59
|
+
|
60
|
+
next unless m = match.match(path.to_s)
|
61
|
+
|
62
|
+
rest = m[1]
|
63
|
+
next unless !rest || rest.empty? || rest[0] == ?/
|
64
|
+
|
65
|
+
env['SCRIPT_NAME'] = (script_name + location)
|
66
|
+
env['PATH_INFO'] = rest
|
67
|
+
|
68
|
+
return app.call(env)
|
69
|
+
end
|
70
|
+
|
71
|
+
[404, {'Content-Type' => "text/plain", "X-Cascade" => "pass"}, ["Not Found: #{path}"]]
|
72
|
+
|
73
|
+
ensure
|
74
|
+
env['PATH_INFO'] = path
|
75
|
+
env['SCRIPT_NAME'] = script_name
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
def casecmp?(v1, v2)
|
80
|
+
# if both nil, or they're the same string
|
81
|
+
return true if v1 == v2
|
82
|
+
|
83
|
+
# if either are nil... (but they're not the same)
|
84
|
+
return false if v1.nil?
|
85
|
+
return false if v2.nil?
|
86
|
+
|
87
|
+
# otherwise check they're not case-insensitive the same
|
88
|
+
v1.casecmp(v2).zero?
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
data/lib/puma/reactor.rb
ADDED
@@ -0,0 +1,210 @@
|
|
1
|
+
require 'puma/util'
|
2
|
+
require 'puma/minissl'
|
3
|
+
|
4
|
+
module Puma
|
5
|
+
class Reactor
|
6
|
+
DefaultSleepFor = 5
|
7
|
+
|
8
|
+
def initialize(server, app_pool)
|
9
|
+
@server = server
|
10
|
+
@events = server.events
|
11
|
+
@app_pool = app_pool
|
12
|
+
|
13
|
+
@mutex = Mutex.new
|
14
|
+
@ready, @trigger = Puma::Util.pipe
|
15
|
+
@input = []
|
16
|
+
@sleep_for = DefaultSleepFor
|
17
|
+
@timeouts = []
|
18
|
+
|
19
|
+
@sockets = [@ready]
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def run_internal
|
25
|
+
sockets = @sockets
|
26
|
+
|
27
|
+
while true
|
28
|
+
begin
|
29
|
+
ready = IO.select sockets, nil, nil, @sleep_for
|
30
|
+
rescue IOError => e
|
31
|
+
if sockets.any? { |socket| socket.closed? }
|
32
|
+
STDERR.puts "Error in select: #{e.message} (#{e.class})"
|
33
|
+
STDERR.puts e.backtrace
|
34
|
+
sockets = sockets.reject { |socket| socket.closed? }
|
35
|
+
retry
|
36
|
+
else
|
37
|
+
raise
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
if ready and reads = ready[0]
|
42
|
+
reads.each do |c|
|
43
|
+
if c == @ready
|
44
|
+
@mutex.synchronize do
|
45
|
+
case @ready.read(1)
|
46
|
+
when "*"
|
47
|
+
sockets += @input
|
48
|
+
@input.clear
|
49
|
+
when "c"
|
50
|
+
sockets.delete_if do |s|
|
51
|
+
if s == @ready
|
52
|
+
false
|
53
|
+
else
|
54
|
+
s.close
|
55
|
+
true
|
56
|
+
end
|
57
|
+
end
|
58
|
+
when "!"
|
59
|
+
return
|
60
|
+
end
|
61
|
+
end
|
62
|
+
else
|
63
|
+
# We have to be sure to remove it from the timeout
|
64
|
+
# list or we'll accidentally close the socket when
|
65
|
+
# it's in use!
|
66
|
+
if c.timeout_at
|
67
|
+
@mutex.synchronize do
|
68
|
+
@timeouts.delete c
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
begin
|
73
|
+
if c.try_to_finish
|
74
|
+
@app_pool << c
|
75
|
+
sockets.delete c
|
76
|
+
end
|
77
|
+
|
78
|
+
# Don't report these to the lowlevel_error handler, otherwise
|
79
|
+
# will be flooding them with errors when persistent connections
|
80
|
+
# are closed.
|
81
|
+
rescue ConnectionError
|
82
|
+
c.write_500
|
83
|
+
c.close
|
84
|
+
|
85
|
+
sockets.delete c
|
86
|
+
|
87
|
+
# SSL handshake failure
|
88
|
+
rescue MiniSSL::SSLError => e
|
89
|
+
@server.lowlevel_error(e, c.env)
|
90
|
+
|
91
|
+
ssl_socket = c.io
|
92
|
+
addr = ssl_socket.peeraddr.last
|
93
|
+
cert = ssl_socket.peercert
|
94
|
+
|
95
|
+
c.close
|
96
|
+
sockets.delete c
|
97
|
+
|
98
|
+
@events.ssl_error @server, addr, cert, e
|
99
|
+
|
100
|
+
# The client doesn't know HTTP well
|
101
|
+
rescue HttpParserError => e
|
102
|
+
@server.lowlevel_error(e, c.env)
|
103
|
+
|
104
|
+
c.write_400
|
105
|
+
c.close
|
106
|
+
|
107
|
+
sockets.delete c
|
108
|
+
|
109
|
+
@events.parse_error @server, c.env, e
|
110
|
+
rescue StandardError => e
|
111
|
+
@server.lowlevel_error(e, c.env)
|
112
|
+
|
113
|
+
c.write_500
|
114
|
+
c.close
|
115
|
+
|
116
|
+
sockets.delete c
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
unless @timeouts.empty?
|
123
|
+
@mutex.synchronize do
|
124
|
+
now = Time.now
|
125
|
+
|
126
|
+
while @timeouts.first.timeout_at < now
|
127
|
+
c = @timeouts.shift
|
128
|
+
c.write_408 if c.in_data_phase
|
129
|
+
c.close
|
130
|
+
sockets.delete c
|
131
|
+
|
132
|
+
break if @timeouts.empty?
|
133
|
+
end
|
134
|
+
|
135
|
+
calculate_sleep
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
public
|
142
|
+
|
143
|
+
def run
|
144
|
+
run_internal
|
145
|
+
ensure
|
146
|
+
@trigger.close
|
147
|
+
@ready.close
|
148
|
+
end
|
149
|
+
|
150
|
+
def run_in_thread
|
151
|
+
@thread = Thread.new do
|
152
|
+
begin
|
153
|
+
run_internal
|
154
|
+
rescue StandardError => e
|
155
|
+
STDERR.puts "Error in reactor loop escaped: #{e.message} (#{e.class})"
|
156
|
+
STDERR.puts e.backtrace
|
157
|
+
retry
|
158
|
+
ensure
|
159
|
+
@trigger.close
|
160
|
+
@ready.close
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def calculate_sleep
|
166
|
+
if @timeouts.empty?
|
167
|
+
@sleep_for = DefaultSleepFor
|
168
|
+
else
|
169
|
+
diff = @timeouts.first.timeout_at.to_f - Time.now.to_f
|
170
|
+
|
171
|
+
if diff < 0.0
|
172
|
+
@sleep_for = 0
|
173
|
+
else
|
174
|
+
@sleep_for = diff
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def add(c)
|
180
|
+
@mutex.synchronize do
|
181
|
+
@input << c
|
182
|
+
@trigger << "*"
|
183
|
+
|
184
|
+
if c.timeout_at
|
185
|
+
@timeouts << c
|
186
|
+
@timeouts.sort! { |a,b| a.timeout_at <=> b.timeout_at }
|
187
|
+
|
188
|
+
calculate_sleep
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
# Close all watched sockets and clear them from being watched
|
194
|
+
def clear!
|
195
|
+
begin
|
196
|
+
@trigger << "c"
|
197
|
+
rescue IOError
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
def shutdown
|
202
|
+
begin
|
203
|
+
@trigger << "!"
|
204
|
+
rescue IOError
|
205
|
+
end
|
206
|
+
|
207
|
+
@thread.join
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|