puma 3.11.2 → 6.0.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of puma might be problematic. Click here for more details.

Files changed (99) hide show
  1. checksums.yaml +5 -5
  2. data/History.md +1708 -422
  3. data/LICENSE +23 -20
  4. data/README.md +190 -64
  5. data/bin/puma-wild +3 -9
  6. data/docs/architecture.md +59 -21
  7. data/docs/compile_options.md +55 -0
  8. data/docs/deployment.md +69 -58
  9. data/docs/fork_worker.md +31 -0
  10. data/docs/images/puma-connection-flow-no-reactor.png +0 -0
  11. data/docs/images/puma-connection-flow.png +0 -0
  12. data/docs/images/puma-general-arch.png +0 -0
  13. data/docs/jungle/README.md +9 -0
  14. data/docs/jungle/rc.d/README.md +74 -0
  15. data/docs/jungle/rc.d/puma +61 -0
  16. data/docs/jungle/rc.d/puma.conf +10 -0
  17. data/docs/kubernetes.md +66 -0
  18. data/docs/nginx.md +1 -1
  19. data/docs/plugins.md +22 -12
  20. data/docs/rails_dev_mode.md +28 -0
  21. data/docs/restart.md +47 -22
  22. data/docs/signals.md +13 -11
  23. data/docs/stats.md +142 -0
  24. data/docs/systemd.md +100 -115
  25. data/docs/testing_benchmarks_local_files.md +150 -0
  26. data/docs/testing_test_rackup_ci_files.md +36 -0
  27. data/ext/puma_http11/PumaHttp11Service.java +2 -2
  28. data/ext/puma_http11/ext_help.h +1 -1
  29. data/ext/puma_http11/extconf.rb +61 -3
  30. data/ext/puma_http11/http11_parser.c +106 -118
  31. data/ext/puma_http11/http11_parser.h +2 -2
  32. data/ext/puma_http11/http11_parser.java.rl +22 -38
  33. data/ext/puma_http11/http11_parser.rl +6 -4
  34. data/ext/puma_http11/http11_parser_common.rl +6 -6
  35. data/ext/puma_http11/mini_ssl.c +376 -93
  36. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  37. data/ext/puma_http11/org/jruby/puma/Http11.java +108 -116
  38. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +84 -99
  39. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +250 -88
  40. data/ext/puma_http11/puma_http11.c +49 -57
  41. data/lib/puma/app/status.rb +71 -49
  42. data/lib/puma/binder.rb +243 -148
  43. data/lib/puma/cli.rb +50 -35
  44. data/lib/puma/client.rb +369 -232
  45. data/lib/puma/cluster/worker.rb +175 -0
  46. data/lib/puma/cluster/worker_handle.rb +97 -0
  47. data/lib/puma/cluster.rb +268 -235
  48. data/lib/puma/commonlogger.rb +4 -2
  49. data/lib/puma/configuration.rb +116 -88
  50. data/lib/puma/const.rb +49 -30
  51. data/lib/puma/control_cli.rb +124 -77
  52. data/lib/puma/detect.rb +33 -2
  53. data/lib/puma/dsl.rb +685 -138
  54. data/lib/puma/error_logger.rb +112 -0
  55. data/lib/puma/events.rb +17 -111
  56. data/lib/puma/io_buffer.rb +34 -5
  57. data/lib/puma/jruby_restart.rb +4 -59
  58. data/lib/puma/json_serialization.rb +96 -0
  59. data/lib/puma/launcher/bundle_pruner.rb +104 -0
  60. data/lib/puma/launcher.rb +197 -130
  61. data/lib/puma/log_writer.rb +137 -0
  62. data/lib/puma/minissl/context_builder.rb +92 -0
  63. data/lib/puma/minissl.rb +256 -70
  64. data/lib/puma/null_io.rb +20 -1
  65. data/lib/puma/plugin/tmp_restart.rb +3 -1
  66. data/lib/puma/plugin.rb +9 -13
  67. data/lib/puma/rack/builder.rb +8 -9
  68. data/lib/puma/rack/urlmap.rb +2 -0
  69. data/lib/puma/rack_default.rb +3 -1
  70. data/lib/puma/reactor.rb +90 -187
  71. data/lib/puma/request.rb +607 -0
  72. data/lib/puma/runner.rb +94 -71
  73. data/lib/puma/server.rb +336 -703
  74. data/lib/puma/single.rb +27 -72
  75. data/lib/puma/state_file.rb +46 -7
  76. data/lib/puma/systemd.rb +47 -0
  77. data/lib/puma/thread_pool.rb +185 -91
  78. data/lib/puma/util.rb +23 -10
  79. data/lib/puma.rb +68 -3
  80. data/lib/rack/handler/puma.rb +17 -14
  81. data/tools/Dockerfile +16 -0
  82. data/tools/trickletest.rb +0 -1
  83. metadata +53 -30
  84. data/ext/puma_http11/io_buffer.c +0 -155
  85. data/lib/puma/accept_nonblock.rb +0 -23
  86. data/lib/puma/compat.rb +0 -14
  87. data/lib/puma/convenient.rb +0 -23
  88. data/lib/puma/daemon_ext.rb +0 -31
  89. data/lib/puma/delegation.rb +0 -11
  90. data/lib/puma/java_io_buffer.rb +0 -45
  91. data/lib/puma/rack/backports/uri/common_193.rb +0 -33
  92. data/lib/puma/tcp_logger.rb +0 -39
  93. data/tools/jungle/README.md +0 -13
  94. data/tools/jungle/init.d/README.md +0 -59
  95. data/tools/jungle/init.d/puma +0 -421
  96. data/tools/jungle/init.d/run-puma +0 -18
  97. data/tools/jungle/upstart/README.md +0 -61
  98. data/tools/jungle/upstart/puma-manager.conf +0 -31
  99. data/tools/jungle/upstart/puma.conf +0 -69
data/lib/puma/launcher.rb CHANGED
@@ -1,12 +1,12 @@
1
- require 'puma/events'
2
- require 'puma/detect'
1
+ # frozen_string_literal: true
3
2
 
4
- require 'puma/cluster'
5
- require 'puma/single'
6
-
7
- require 'puma/const'
8
-
9
- require 'puma/binder'
3
+ require_relative 'log_writer'
4
+ require_relative 'events'
5
+ require_relative 'detect'
6
+ require_relative 'cluster'
7
+ require_relative 'single'
8
+ require_relative 'const'
9
+ require_relative 'binder'
10
10
 
11
11
  module Puma
12
12
  # Puma::Launcher is the single entry point for starting a Puma server based on user
@@ -16,17 +16,14 @@ module Puma
16
16
  # It is responsible for either launching a cluster of Puma workers or a single
17
17
  # puma server.
18
18
  class Launcher
19
- KEYS_NOT_TO_PERSIST_IN_STATE = [
20
- :logger, :lowlevel_error_handler,
21
- :before_worker_shutdown, :before_worker_boot, :before_worker_fork,
22
- :after_worker_boot, :before_fork, :on_restart
23
- ]
19
+ autoload :BundlePruner, 'puma/launcher/bundle_pruner'
20
+
24
21
  # Returns an instance of Launcher
25
22
  #
26
23
  # +conf+ A Puma::Configuration object indicating how to run the server.
27
24
  #
28
25
  # +launcher_args+ A Hash that currently has one required key `:events`,
29
- # this is expected to hold an object similar to an `Puma::Events.stdio`,
26
+ # this is expected to hold an object similar to an `Puma::LogWriter.stdio`,
30
27
  # this object will be responsible for broadcasting Puma's internal state
31
28
  # to a logging destination. An optional key `:argv` can be supplied,
32
29
  # this should be an array of strings, these arguments are re-used when
@@ -40,57 +37,69 @@ module Puma
40
37
  # [200, {}, ["hello world"]]
41
38
  # end
42
39
  # end
43
- # Puma::Launcher.new(conf, events: Puma::Events.stdio).run
40
+ # Puma::Launcher.new(conf, log_writer: Puma::LogWriter.stdio).run
44
41
  def initialize(conf, launcher_args={})
45
42
  @runner = nil
46
- @events = launcher_args[:events] || Events::DEFAULT
43
+ @log_writer = launcher_args[:log_writer] || LogWriter::DEFAULT
44
+ @events = launcher_args[:events] || Events.new
47
45
  @argv = launcher_args[:argv] || []
48
46
  @original_argv = @argv.dup
49
47
  @config = conf
50
48
 
51
- @binder = Binder.new(@events)
52
- @binder.import_from_env
53
-
54
- @environment = conf.environment
49
+ @config.options[:log_writer] = @log_writer
55
50
 
56
51
  # Advertise the Configuration
57
52
  Puma.cli_config = @config if defined?(Puma.cli_config)
58
53
 
59
54
  @config.load
60
55
 
56
+ @binder = Binder.new(@log_writer, conf)
57
+ @binder.create_inherited_fds(ENV).each { |k| ENV.delete k }
58
+ @binder.create_activated_fds(ENV).each { |k| ENV.delete k }
59
+
60
+ @environment = conf.environment
61
+
62
+ if @config.options[:bind_to_activated_sockets]
63
+ @config.options[:binds] = @binder.synthesize_binds_from_activated_fs(
64
+ @config.options[:binds],
65
+ @config.options[:bind_to_activated_sockets] == 'only'
66
+ )
67
+ end
68
+
61
69
  @options = @config.options
62
70
  @config.clamp
63
71
 
64
- generate_restart_data
72
+ @log_writer.formatter = LogWriter::PidFormatter.new if clustered?
73
+ @log_writer.formatter = options[:log_formatter] if @options[:log_formatter]
65
74
 
66
- if clustered? && (Puma.jruby? || Puma.windows?)
67
- unsupported 'worker mode not supported on JRuby or Windows'
68
- end
75
+ generate_restart_data
69
76
 
70
- if @options[:daemon] && Puma.windows?
71
- unsupported 'daemon mode not supported on Windows'
77
+ if clustered? && !Puma.forkable?
78
+ unsupported "worker mode not supported on #{RUBY_ENGINE} on this platform"
72
79
  end
73
80
 
74
81
  Dir.chdir(@restart_dir)
75
82
 
76
- prune_bundler if prune_bundler?
83
+ prune_bundler!
77
84
 
78
85
  @environment = @options[:environment] if @options[:environment]
79
86
  set_rack_environment
80
87
 
81
88
  if clustered?
82
- @events.formatter = Events::PidFormatter.new
83
- @options[:logger] = @events
89
+ @options[:logger] = @log_writer
84
90
 
85
- @runner = Cluster.new(self, @events)
91
+ @runner = Cluster.new(self)
86
92
  else
87
- @runner = Single.new(self, @events)
93
+ @runner = Single.new(self)
88
94
  end
95
+ Puma.stats_object = @runner
89
96
 
90
97
  @status = :run
98
+
99
+ log_config if ENV['PUMA_LOG_CONFIG']
91
100
  end
92
101
 
93
- attr_reader :binder, :events, :config, :options, :restart_dir
102
+ attr_reader :binder, :log_writer, :events, :config, :options, :restart_dir
94
103
 
95
104
  # Return stats about the server
96
105
  def stats
@@ -103,16 +112,18 @@ module Puma
103
112
  write_pid
104
113
 
105
114
  path = @options[:state]
115
+ permission = @options[:state_permission]
106
116
  return unless path
107
117
 
108
- require 'puma/state_file'
118
+ require_relative 'state_file'
109
119
 
110
120
  sf = StateFile.new
111
121
  sf.pid = Process.pid
112
122
  sf.control_url = @options[:control_url]
113
123
  sf.control_auth_token = @options[:control_auth_token]
124
+ sf.running_from = File.expand_path('.')
114
125
 
115
- sf.save path
126
+ sf.save path, permission
116
127
  end
117
128
 
118
129
  # Delete the configured pidfile
@@ -121,19 +132,6 @@ module Puma
121
132
  File.unlink(path) if path && File.exist?(path)
122
133
  end
123
134
 
124
- # If configured, write the pid of the current process out
125
- # to a file.
126
- def write_pid
127
- path = @options[:pidfile]
128
- return unless path
129
-
130
- File.open(path, 'w') { |f| f.puts Process.pid }
131
- cur = Process.pid
132
- at_exit do
133
- delete_pidfile if cur == Process.pid
134
- end
135
- end
136
-
137
135
  # Begin async shutdown of the server
138
136
  def halt
139
137
  @status = :halt
@@ -161,18 +159,20 @@ module Puma
161
159
  true
162
160
  end
163
161
 
162
+ # Begin a refork if supported
163
+ def refork
164
+ if clustered? && @runner.respond_to?(:fork_worker!) && @options[:fork_worker]
165
+ @runner.fork_worker!
166
+ true
167
+ else
168
+ log "* refork called but not available."
169
+ false
170
+ end
171
+ end
172
+
164
173
  # Run the server. This blocks until the server is stopped
165
174
  def run
166
- previous_env =
167
- if defined?(Bundler)
168
- env = Bundler::ORIGINAL_ENV.dup
169
- # add -rbundler/setup so we load from Gemfile when restarting
170
- bundle = "-rbundler/setup"
171
- env["RUBYOPT"] = [env["RUBYOPT"], bundle].join(" ").lstrip unless env["RUBYOPT"].to_s.include?(bundle)
172
- env
173
- else
174
- ENV.to_h
175
- end
175
+ previous_env = get_env
176
176
 
177
177
  @config.clamp
178
178
 
@@ -180,28 +180,22 @@ module Puma
180
180
 
181
181
  setup_signals
182
182
  set_process_title
183
+ integrate_with_systemd
184
+
185
+ # This blocks until the server is stopped
183
186
  @runner.run
184
187
 
185
- case @status
186
- when :halt
187
- log "* Stopping immediately!"
188
- when :run, :stop
189
- graceful_stop
190
- when :restart
191
- log "* Restarting..."
192
- ENV.replace(previous_env)
193
- @runner.before_restart
194
- restart!
195
- when :exit
196
- # nothing
197
- end
188
+ do_run_finished(previous_env)
198
189
  end
199
190
 
200
- # Return which tcp port the launcher is using, if it's using TCP
201
- def connected_port
202
- @binder.connected_port
191
+ # Return all tcp ports the launcher may be using, TCP or SSL
192
+ # @!attribute [r] connected_ports
193
+ # @version 5.0.0
194
+ def connected_ports
195
+ @binder.connected_ports
203
196
  end
204
197
 
198
+ # @!attribute [r] restart_args
205
199
  def restart_args
206
200
  cmd = @options[:restart_cmd]
207
201
  if cmd
@@ -211,19 +205,80 @@ module Puma
211
205
  end
212
206
  end
213
207
 
208
+ def close_binder_listeners
209
+ @runner.close_control_listeners
210
+ @binder.close_listeners
211
+ unless @status == :restart
212
+ log "=== puma shutdown: #{Time.now} ==="
213
+ log "- Goodbye!"
214
+ end
215
+ end
216
+
217
+ # @!attribute [r] thread_status
218
+ # @version 5.0.0
219
+ def thread_status
220
+ Thread.list.each do |thread|
221
+ name = "Thread: TID-#{thread.object_id.to_s(36)}"
222
+ name += " #{thread['label']}" if thread['label']
223
+ name += " #{thread.name}" if thread.respond_to?(:name) && thread.name
224
+ backtrace = thread.backtrace || ["<no backtrace available>"]
225
+
226
+ yield name, backtrace
227
+ end
228
+ end
229
+
214
230
  private
215
231
 
216
- def reload_worker_directory
217
- @runner.reload_worker_directory if @runner.respond_to?(:reload_worker_directory)
232
+ def get_env
233
+ if defined?(Bundler)
234
+ env = Bundler::ORIGINAL_ENV.dup
235
+ # add -rbundler/setup so we load from Gemfile when restarting
236
+ bundle = "-rbundler/setup"
237
+ env["RUBYOPT"] = [env["RUBYOPT"], bundle].join(" ").lstrip unless env["RUBYOPT"].to_s.include?(bundle)
238
+ env
239
+ else
240
+ ENV.to_h
241
+ end
242
+ end
243
+
244
+ def do_run_finished(previous_env)
245
+ case @status
246
+ when :halt
247
+ do_forceful_stop
248
+ when :run, :stop
249
+ do_graceful_stop
250
+ when :restart
251
+ do_restart(previous_env)
252
+ end
253
+
254
+ close_binder_listeners unless @status == :restart
255
+ end
256
+
257
+ def do_forceful_stop
258
+ log "* Stopping immediately!"
259
+ @runner.stop_control
260
+ end
261
+
262
+ def do_graceful_stop
263
+ @events.fire_on_stopped!
264
+ @runner.stop_blocked
265
+ end
266
+
267
+ def do_restart(previous_env)
268
+ log "* Restarting..."
269
+ ENV.replace(previous_env)
270
+ @runner.stop_control
271
+ restart!
218
272
  end
219
273
 
220
274
  def restart!
221
- @config.run_hooks :on_restart, self
275
+ @events.fire_on_restart!
276
+ @config.run_hooks :on_restart, self, @log_writer
222
277
 
223
278
  if Puma.jruby?
224
279
  close_binder_listeners
225
280
 
226
- require 'puma/jruby_restart'
281
+ require_relative 'jruby_restart'
227
282
  JRubyRestart.chdir_exec(@restart_dir, restart_args)
228
283
  elsif Puma.windows?
229
284
  close_binder_listeners
@@ -232,50 +287,53 @@ module Puma
232
287
  Dir.chdir(@restart_dir)
233
288
  Kernel.exec(*argv)
234
289
  else
235
- redirects = {:close_others => true}
236
- @binder.listeners.each_with_index do |(l, io), i|
237
- ENV["PUMA_INHERIT_#{i}"] = "#{io.to_i}:#{l}"
238
- redirects[io.to_i] = io.to_i
239
- end
240
-
241
290
  argv = restart_args
242
291
  Dir.chdir(@restart_dir)
243
- argv += [redirects] if RUBY_VERSION >= '1.9'
292
+ ENV.update(@binder.redirects_for_restart_env)
293
+ argv += [@binder.redirects_for_restart]
244
294
  Kernel.exec(*argv)
245
295
  end
246
296
  end
247
297
 
248
- def prune_bundler
249
- return unless defined?(Bundler)
250
- puma = Bundler.rubygems.loaded_specs("puma")
251
- dirs = puma.require_paths.map { |x| File.join(puma.full_gem_path, x) }
252
- puma_lib_dir = dirs.detect { |x| File.exist? File.join(x, '../bin/puma-wild') }
298
+ # If configured, write the pid of the current process out
299
+ # to a file.
300
+ def write_pid
301
+ path = @options[:pidfile]
302
+ return unless path
303
+ cur_pid = Process.pid
304
+ File.write path, cur_pid, mode: 'wb:UTF-8'
305
+ at_exit do
306
+ delete_pidfile if cur_pid == Process.pid
307
+ end
308
+ end
253
309
 
254
- unless puma_lib_dir
255
- log "! Unable to prune Bundler environment, continuing"
310
+ def reload_worker_directory
311
+ @runner.reload_worker_directory if @runner.respond_to?(:reload_worker_directory)
312
+ end
313
+
314
+ # Puma's systemd integration allows Puma to inform systemd:
315
+ # 1. when it has successfully started
316
+ # 2. when it is starting shutdown
317
+ # 3. periodically for a liveness check with a watchdog thread
318
+ def integrate_with_systemd
319
+ return unless ENV["NOTIFY_SOCKET"]
320
+
321
+ begin
322
+ require_relative 'systemd'
323
+ rescue LoadError
324
+ log "Systemd integration failed. It looks like you're trying to use systemd notify but don't have sd_notify gem installed"
256
325
  return
257
326
  end
258
327
 
259
- deps = puma.runtime_dependencies.map do |d|
260
- spec = Bundler.rubygems.loaded_specs(d.name)
261
- "#{d.name}:#{spec.version.to_s}"
262
- end
328
+ log "* Enabling systemd notification integration"
263
329
 
264
- log '* Pruning Bundler environment'
265
- home = ENV['GEM_HOME']
266
- Bundler.with_clean_env do
267
- ENV['GEM_HOME'] = home
268
- ENV['PUMA_BUNDLER_PRUNED'] = '1'
269
- wild = File.expand_path(File.join(puma_lib_dir, "../bin/puma-wild"))
270
- args = [Gem.ruby, wild, '-I', dirs.join(':'), deps.join(',')] + @original_argv
271
- # Ruby 2.0+ defaults to true which breaks socket activation
272
- args += [{:close_others => false}] if RUBY_VERSION >= '2.0'
273
- Kernel.exec(*args)
274
- end
330
+ systemd = Systemd.new(@log_writer, @events)
331
+ systemd.hook_events
332
+ systemd.start_watchdog
275
333
  end
276
334
 
277
335
  def log(str)
278
- @events.log str
336
+ @log_writer.log(str)
279
337
  end
280
338
 
281
339
  def clustered?
@@ -283,20 +341,15 @@ module Puma
283
341
  end
284
342
 
285
343
  def unsupported(str)
286
- @events.error(str)
344
+ @log_writer.error(str)
287
345
  raise UnsupportedOption
288
346
  end
289
347
 
290
- def graceful_stop
291
- @runner.stop_blocked
292
- log "=== puma shutdown: #{Time.now} ==="
293
- log "- Goodbye!"
294
- end
295
-
296
348
  def set_process_title
297
349
  Process.respond_to?(:setproctitle) ? Process.setproctitle(title) : $0 = title
298
350
  end
299
351
 
352
+ # @!attribute [r] title
300
353
  def title
301
354
  buffer = "puma #{Puma::Const::VERSION} (#{@options[:binds].join(',')})"
302
355
  buffer += " [#{@options[:tag]}]" if @options[:tag] && !@options[:tag].empty?
@@ -308,6 +361,7 @@ module Puma
308
361
  ENV['RACK_ENV'] = environment
309
362
  end
310
363
 
364
+ # @!attribute [r] environment
311
365
  def environment
312
366
  @environment
313
367
  end
@@ -316,16 +370,11 @@ module Puma
316
370
  @options[:prune_bundler] && clustered? && !@options[:preload_app]
317
371
  end
318
372
 
319
- def close_binder_listeners
320
- @binder.listeners.each do |l, io|
321
- io.close
322
- uri = URI.parse(l)
323
- next unless uri.scheme == 'unix'
324
- File.unlink("#{uri.host}#{uri.path}")
325
- end
373
+ def prune_bundler!
374
+ return unless prune_bundler?
375
+ BundlePruner.new(@original_argv, @options[:extra_runtime_dependencies], @log_writer).prune
326
376
  end
327
377
 
328
-
329
378
  def generate_restart_data
330
379
  if dir = @options[:directory]
331
380
  @restart_dir = dir
@@ -392,9 +441,10 @@ module Puma
392
441
 
393
442
  begin
394
443
  Signal.trap "SIGTERM" do
395
- graceful_stop
444
+ # Shortcut the control flow in case raise_exception_on_sigterm is true
445
+ do_graceful_stop
396
446
 
397
- raise SignalException, "SIGTERM"
447
+ raise(SignalException, "SIGTERM") if @options[:raise_exception_on_sigterm]
398
448
  end
399
449
  rescue Exception
400
450
  log "*** SIGTERM not implemented, signal based gracefully stopping unavailable!"
@@ -402,12 +452,6 @@ module Puma
402
452
 
403
453
  begin
404
454
  Signal.trap "SIGINT" do
405
- if Puma.jruby?
406
- @status = :exit
407
- graceful_stop
408
- exit
409
- end
410
-
411
455
  stop
412
456
  end
413
457
  rescue Exception
@@ -425,6 +469,29 @@ module Puma
425
469
  rescue Exception
426
470
  log "*** SIGHUP not implemented, signal based logs reopening unavailable!"
427
471
  end
472
+
473
+ begin
474
+ unless Puma.jruby? # INFO in use by JVM already
475
+ Signal.trap "SIGINFO" do
476
+ thread_status do |name, backtrace|
477
+ @log_writer.log(name)
478
+ @log_writer.log(backtrace.map { |bt| " #{bt}" })
479
+ end
480
+ end
481
+ end
482
+ rescue Exception
483
+ # Not going to log this one, as SIGINFO is *BSD only and would be pretty annoying
484
+ # to see this constantly on Linux.
485
+ end
486
+ end
487
+
488
+ def log_config
489
+ log "Configuration:"
490
+
491
+ @config.final_options
492
+ .each { |config_key, value| log "- #{config_key}: #{value}" }
493
+
494
+ log "\n"
428
495
  end
429
496
  end
430
497
  end
@@ -0,0 +1,137 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'null_io'
4
+ require_relative 'error_logger'
5
+ require 'stringio'
6
+ require 'io/wait' unless Puma::HAS_NATIVE_IO_WAIT
7
+
8
+ module Puma
9
+
10
+ # Handles logging concerns for both standard messages
11
+ # (+stdout+) and errors (+stderr+).
12
+ class LogWriter
13
+
14
+ class DefaultFormatter
15
+ def call(str)
16
+ str
17
+ end
18
+ end
19
+
20
+ class PidFormatter
21
+ def call(str)
22
+ "[#{$$}] #{str}"
23
+ end
24
+ end
25
+
26
+ LOG_QUEUE = Queue.new
27
+
28
+ attr_reader :stdout,
29
+ :stderr
30
+
31
+ attr_accessor :formatter
32
+
33
+ # Create a LogWriter that prints to +stdout+ and +stderr+.
34
+ def initialize(stdout, stderr)
35
+ @formatter = DefaultFormatter.new
36
+ @stdout = stdout
37
+ @stderr = stderr
38
+
39
+ @debug = ENV.key?('PUMA_DEBUG')
40
+ @error_logger = ErrorLogger.new(@stderr)
41
+ end
42
+
43
+ DEFAULT = new(STDOUT, STDERR)
44
+
45
+ # Returns an LogWriter object which writes its status to
46
+ # two StringIO objects.
47
+ def self.strings
48
+ LogWriter.new(StringIO.new, StringIO.new)
49
+ end
50
+
51
+ def self.stdio
52
+ LogWriter.new($stdout, $stderr)
53
+ end
54
+
55
+ def self.null
56
+ n = NullIO.new
57
+ LogWriter.new(n, n)
58
+ end
59
+
60
+ # Write +str+ to +@stdout+
61
+ def log(str)
62
+ internal_write "#{@formatter.call str}\n"
63
+ end
64
+
65
+ def write(str)
66
+ internal_write @formatter.call(str)
67
+ end
68
+
69
+ def internal_write(str)
70
+ LOG_QUEUE << str
71
+ while (w_str = LOG_QUEUE.pop(true)) do
72
+ begin
73
+ @stdout.is_a?(IO) and @stdout.wait_writable(1)
74
+ @stdout.write w_str
75
+ @stdout.flush unless @stdout.sync
76
+ rescue Errno::EPIPE, Errno::EBADF, IOError
77
+ end
78
+ end
79
+ rescue ThreadError
80
+ end
81
+ private :internal_write
82
+
83
+ def debug(str)
84
+ log("% #{str}") if @debug
85
+ end
86
+
87
+ # Write +str+ to +@stderr+
88
+ def error(str)
89
+ @error_logger.info(text: @formatter.call("ERROR: #{str}"))
90
+ exit 1
91
+ end
92
+
93
+ def format(str)
94
+ formatter.call(str)
95
+ end
96
+
97
+ # An HTTP connection error has occurred.
98
+ # +error+ a connection exception, +req+ the request,
99
+ # and +text+ additional info
100
+ # @version 5.0.0
101
+ def connection_error(error, req, text="HTTP connection error")
102
+ @error_logger.info(error: error, req: req, text: text)
103
+ end
104
+
105
+ # An HTTP parse error has occurred.
106
+ # +error+ a parsing exception,
107
+ # and +req+ the request.
108
+ def parse_error(error, req)
109
+ @error_logger.info(error: error, req: req, text: 'HTTP parse error, malformed request')
110
+ end
111
+
112
+ # An SSL error has occurred.
113
+ # @param error <Puma::MiniSSL::SSLError>
114
+ # @param ssl_socket <Puma::MiniSSL::Socket>
115
+ def ssl_error(error, ssl_socket)
116
+ peeraddr = ssl_socket.peeraddr.last rescue "<unknown>"
117
+ peercert = ssl_socket.peercert
118
+ subject = peercert ? peercert.subject : nil
119
+ @error_logger.info(error: error, text: "SSL error, peer: #{peeraddr}, peer cert: #{subject}")
120
+ end
121
+
122
+ # An unknown error has occurred.
123
+ # +error+ an exception object, +req+ the request,
124
+ # and +text+ additional info
125
+ def unknown_error(error, req=nil, text="Unknown error")
126
+ @error_logger.info(error: error, req: req, text: text)
127
+ end
128
+
129
+ # Log occurred error debug dump.
130
+ # +error+ an exception object, +req+ the request,
131
+ # and +text+ additional info
132
+ # @version 5.0.0
133
+ def debug_error(error, req=nil, text="")
134
+ @error_logger.debug(error: error, req: req, text: text)
135
+ end
136
+ end
137
+ end