puma-simon 3.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (157) hide show
  1. checksums.yaml +7 -0
  2. data/.github/issue_template.md +20 -0
  3. data/.gitignore +18 -0
  4. data/.hoeignore +12 -0
  5. data/.travis.yml +29 -0
  6. data/DEPLOYMENT.md +91 -0
  7. data/Gemfile +12 -0
  8. data/History.md +1254 -0
  9. data/LICENSE +26 -0
  10. data/Manifest.txt +78 -0
  11. data/README.md +353 -0
  12. data/Rakefile +158 -0
  13. data/Release.md +9 -0
  14. data/bin/puma +10 -0
  15. data/bin/puma-wild +31 -0
  16. data/bin/pumactl +12 -0
  17. data/docs/nginx.md +80 -0
  18. data/docs/signals.md +43 -0
  19. data/docs/systemd.md +197 -0
  20. data/examples/CA/cacert.pem +23 -0
  21. data/examples/CA/newcerts/cert_1.pem +19 -0
  22. data/examples/CA/newcerts/cert_2.pem +19 -0
  23. data/examples/CA/private/cakeypair.pem +30 -0
  24. data/examples/CA/serial +1 -0
  25. data/examples/config.rb +200 -0
  26. data/examples/plugins/redis_stop_puma.rb +46 -0
  27. data/examples/puma/cert_puma.pem +19 -0
  28. data/examples/puma/client-certs/ca.crt +19 -0
  29. data/examples/puma/client-certs/ca.key +27 -0
  30. data/examples/puma/client-certs/client.crt +19 -0
  31. data/examples/puma/client-certs/client.key +27 -0
  32. data/examples/puma/client-certs/client_expired.crt +19 -0
  33. data/examples/puma/client-certs/client_expired.key +27 -0
  34. data/examples/puma/client-certs/client_unknown.crt +19 -0
  35. data/examples/puma/client-certs/client_unknown.key +27 -0
  36. data/examples/puma/client-certs/generate.rb +78 -0
  37. data/examples/puma/client-certs/keystore.jks +0 -0
  38. data/examples/puma/client-certs/server.crt +19 -0
  39. data/examples/puma/client-certs/server.key +27 -0
  40. data/examples/puma/client-certs/server.p12 +0 -0
  41. data/examples/puma/client-certs/unknown_ca.crt +19 -0
  42. data/examples/puma/client-certs/unknown_ca.key +27 -0
  43. data/examples/puma/csr_puma.pem +11 -0
  44. data/examples/puma/keystore.jks +0 -0
  45. data/examples/puma/puma_keypair.pem +15 -0
  46. data/examples/qc_config.rb +13 -0
  47. data/ext/puma_http11/PumaHttp11Service.java +17 -0
  48. data/ext/puma_http11/ext_help.h +15 -0
  49. data/ext/puma_http11/extconf.rb +15 -0
  50. data/ext/puma_http11/http11_parser.c +1069 -0
  51. data/ext/puma_http11/http11_parser.h +65 -0
  52. data/ext/puma_http11/http11_parser.java.rl +161 -0
  53. data/ext/puma_http11/http11_parser.rl +147 -0
  54. data/ext/puma_http11/http11_parser_common.rl +54 -0
  55. data/ext/puma_http11/io_buffer.c +155 -0
  56. data/ext/puma_http11/mini_ssl.c +457 -0
  57. data/ext/puma_http11/org/jruby/puma/Http11.java +234 -0
  58. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +473 -0
  59. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +339 -0
  60. data/ext/puma_http11/puma_http11.c +500 -0
  61. data/gemfiles/2.1-Gemfile +12 -0
  62. data/lib/puma.rb +15 -0
  63. data/lib/puma/accept_nonblock.rb +23 -0
  64. data/lib/puma/app/status.rb +66 -0
  65. data/lib/puma/binder.rb +402 -0
  66. data/lib/puma/cli.rb +220 -0
  67. data/lib/puma/client.rb +434 -0
  68. data/lib/puma/cluster.rb +510 -0
  69. data/lib/puma/commonlogger.rb +106 -0
  70. data/lib/puma/compat.rb +14 -0
  71. data/lib/puma/configuration.rb +364 -0
  72. data/lib/puma/const.rb +224 -0
  73. data/lib/puma/control_cli.rb +259 -0
  74. data/lib/puma/convenient.rb +23 -0
  75. data/lib/puma/daemon_ext.rb +31 -0
  76. data/lib/puma/delegation.rb +11 -0
  77. data/lib/puma/detect.rb +13 -0
  78. data/lib/puma/dsl.rb +486 -0
  79. data/lib/puma/events.rb +152 -0
  80. data/lib/puma/io_buffer.rb +7 -0
  81. data/lib/puma/java_io_buffer.rb +45 -0
  82. data/lib/puma/jruby_restart.rb +83 -0
  83. data/lib/puma/launcher.rb +410 -0
  84. data/lib/puma/minissl.rb +221 -0
  85. data/lib/puma/null_io.rb +42 -0
  86. data/lib/puma/plugin.rb +115 -0
  87. data/lib/puma/plugin/tmp_restart.rb +35 -0
  88. data/lib/puma/rack/backports/uri/common_193.rb +33 -0
  89. data/lib/puma/rack/builder.rb +298 -0
  90. data/lib/puma/rack/urlmap.rb +91 -0
  91. data/lib/puma/rack_default.rb +7 -0
  92. data/lib/puma/reactor.rb +210 -0
  93. data/lib/puma/runner.rb +171 -0
  94. data/lib/puma/server.rb +949 -0
  95. data/lib/puma/single.rb +112 -0
  96. data/lib/puma/state_file.rb +29 -0
  97. data/lib/puma/tcp_logger.rb +39 -0
  98. data/lib/puma/thread_pool.rb +297 -0
  99. data/lib/puma/util.rb +128 -0
  100. data/lib/rack/handler/puma.rb +78 -0
  101. data/puma.gemspec +52 -0
  102. data/test/ab_rs.rb +22 -0
  103. data/test/config.rb +2 -0
  104. data/test/config/app.rb +9 -0
  105. data/test/config/plugin.rb +1 -0
  106. data/test/config/settings.rb +2 -0
  107. data/test/config/state_file_testing_config.rb +14 -0
  108. data/test/hello-bind.ru +2 -0
  109. data/test/hello-delay.ru +3 -0
  110. data/test/hello-map.ru +3 -0
  111. data/test/hello-post.ru +4 -0
  112. data/test/hello-stuck.ru +1 -0
  113. data/test/hello-tcp.ru +5 -0
  114. data/test/hello.ru +1 -0
  115. data/test/hijack.ru +6 -0
  116. data/test/hijack2.ru +5 -0
  117. data/test/lobster.ru +4 -0
  118. data/test/shell/run.sh +24 -0
  119. data/test/shell/t1.rb +19 -0
  120. data/test/shell/t1_conf.rb +3 -0
  121. data/test/shell/t2.rb +17 -0
  122. data/test/shell/t2_conf.rb +6 -0
  123. data/test/shell/t3.rb +25 -0
  124. data/test/shell/t3_conf.rb +5 -0
  125. data/test/slow.ru +4 -0
  126. data/test/ssl_config.rb +4 -0
  127. data/test/test_app_status.rb +93 -0
  128. data/test/test_binder.rb +31 -0
  129. data/test/test_cli.rb +209 -0
  130. data/test/test_config.rb +95 -0
  131. data/test/test_events.rb +161 -0
  132. data/test/test_helper.rb +50 -0
  133. data/test/test_http10.rb +27 -0
  134. data/test/test_http11.rb +186 -0
  135. data/test/test_integration.rb +247 -0
  136. data/test/test_iobuffer.rb +39 -0
  137. data/test/test_minissl.rb +29 -0
  138. data/test/test_null_io.rb +49 -0
  139. data/test/test_persistent.rb +245 -0
  140. data/test/test_puma_server.rb +626 -0
  141. data/test/test_puma_server_ssl.rb +222 -0
  142. data/test/test_rack_handler.rb +57 -0
  143. data/test/test_rack_server.rb +138 -0
  144. data/test/test_tcp_logger.rb +39 -0
  145. data/test/test_tcp_rack.rb +36 -0
  146. data/test/test_thread_pool.rb +250 -0
  147. data/test/test_unix_socket.rb +35 -0
  148. data/test/test_web_server.rb +88 -0
  149. data/tools/jungle/README.md +9 -0
  150. data/tools/jungle/init.d/README.md +59 -0
  151. data/tools/jungle/init.d/puma +421 -0
  152. data/tools/jungle/init.d/run-puma +18 -0
  153. data/tools/jungle/upstart/README.md +61 -0
  154. data/tools/jungle/upstart/puma-manager.conf +31 -0
  155. data/tools/jungle/upstart/puma.conf +69 -0
  156. data/tools/trickletest.rb +45 -0
  157. 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
@@ -0,0 +1,7 @@
1
+ require 'rack/handler/puma'
2
+
3
+ module Rack::Handler
4
+ def self.default(options = {})
5
+ Rack::Handler::Puma
6
+ end
7
+ end
@@ -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