puma-simon 3.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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