puma 3.12.0 → 5.3.1

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 (93) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +1413 -439
  3. data/LICENSE +23 -20
  4. data/README.md +131 -60
  5. data/bin/puma-wild +3 -9
  6. data/docs/architecture.md +24 -19
  7. data/docs/compile_options.md +19 -0
  8. data/docs/deployment.md +38 -13
  9. data/docs/fork_worker.md +33 -0
  10. data/docs/jungle/README.md +9 -0
  11. data/{tools → docs}/jungle/rc.d/README.md +1 -1
  12. data/{tools → docs}/jungle/rc.d/puma +2 -2
  13. data/{tools → docs}/jungle/rc.d/puma.conf +0 -0
  14. data/docs/kubernetes.md +66 -0
  15. data/docs/nginx.md +1 -1
  16. data/docs/plugins.md +20 -10
  17. data/docs/rails_dev_mode.md +29 -0
  18. data/docs/restart.md +47 -22
  19. data/docs/signals.md +7 -6
  20. data/docs/stats.md +142 -0
  21. data/docs/systemd.md +48 -70
  22. data/ext/puma_http11/PumaHttp11Service.java +2 -2
  23. data/ext/puma_http11/ext_help.h +1 -1
  24. data/ext/puma_http11/extconf.rb +27 -0
  25. data/ext/puma_http11/http11_parser.c +84 -109
  26. data/ext/puma_http11/http11_parser.h +1 -1
  27. data/ext/puma_http11/http11_parser.java.rl +22 -38
  28. data/ext/puma_http11/http11_parser.rl +4 -2
  29. data/ext/puma_http11/http11_parser_common.rl +3 -3
  30. data/ext/puma_http11/mini_ssl.c +262 -87
  31. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  32. data/ext/puma_http11/org/jruby/puma/Http11.java +108 -116
  33. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +89 -106
  34. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +92 -22
  35. data/ext/puma_http11/puma_http11.c +34 -50
  36. data/lib/puma/app/status.rb +68 -49
  37. data/lib/puma/binder.rb +197 -144
  38. data/lib/puma/cli.rb +17 -15
  39. data/lib/puma/client.rb +257 -226
  40. data/lib/puma/cluster/worker.rb +176 -0
  41. data/lib/puma/cluster/worker_handle.rb +90 -0
  42. data/lib/puma/cluster.rb +223 -212
  43. data/lib/puma/commonlogger.rb +4 -2
  44. data/lib/puma/configuration.rb +58 -51
  45. data/lib/puma/const.rb +41 -19
  46. data/lib/puma/control_cli.rb +117 -73
  47. data/lib/puma/detect.rb +26 -3
  48. data/lib/puma/dsl.rb +531 -123
  49. data/lib/puma/error_logger.rb +104 -0
  50. data/lib/puma/events.rb +57 -31
  51. data/lib/puma/io_buffer.rb +9 -5
  52. data/lib/puma/jruby_restart.rb +2 -58
  53. data/lib/puma/json.rb +96 -0
  54. data/lib/puma/launcher.rb +182 -70
  55. data/lib/puma/minissl/context_builder.rb +79 -0
  56. data/lib/puma/minissl.rb +149 -48
  57. data/lib/puma/null_io.rb +15 -1
  58. data/lib/puma/plugin/tmp_restart.rb +2 -0
  59. data/lib/puma/plugin.rb +8 -12
  60. data/lib/puma/queue_close.rb +26 -0
  61. data/lib/puma/rack/builder.rb +4 -5
  62. data/lib/puma/rack/urlmap.rb +2 -0
  63. data/lib/puma/rack_default.rb +2 -0
  64. data/lib/puma/reactor.rb +87 -316
  65. data/lib/puma/request.rb +456 -0
  66. data/lib/puma/runner.rb +33 -52
  67. data/lib/puma/server.rb +288 -679
  68. data/lib/puma/single.rb +13 -67
  69. data/lib/puma/state_file.rb +10 -3
  70. data/lib/puma/systemd.rb +46 -0
  71. data/lib/puma/thread_pool.rb +131 -81
  72. data/lib/puma/util.rb +14 -6
  73. data/lib/puma.rb +54 -0
  74. data/lib/rack/handler/puma.rb +8 -6
  75. data/tools/Dockerfile +16 -0
  76. data/tools/trickletest.rb +0 -1
  77. metadata +45 -29
  78. data/ext/puma_http11/io_buffer.c +0 -155
  79. data/lib/puma/accept_nonblock.rb +0 -23
  80. data/lib/puma/compat.rb +0 -14
  81. data/lib/puma/convenient.rb +0 -23
  82. data/lib/puma/daemon_ext.rb +0 -31
  83. data/lib/puma/delegation.rb +0 -11
  84. data/lib/puma/java_io_buffer.rb +0 -45
  85. data/lib/puma/rack/backports/uri/common_193.rb +0 -33
  86. data/lib/puma/tcp_logger.rb +0 -39
  87. data/tools/jungle/README.md +0 -19
  88. data/tools/jungle/init.d/README.md +0 -61
  89. data/tools/jungle/init.d/puma +0 -421
  90. data/tools/jungle/init.d/run-puma +0 -18
  91. data/tools/jungle/upstart/README.md +0 -61
  92. data/tools/jungle/upstart/puma-manager.conf +0 -31
  93. data/tools/jungle/upstart/puma.conf +0 -69
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'puma/rack/builder'
2
4
  require 'puma/plugin'
3
5
  require 'puma/const'
@@ -18,7 +20,7 @@ module Puma
18
20
  # In this class any "user" specified options take precedence over any
19
21
  # "file" specified options, take precedence over any "default" options.
20
22
  #
21
- # User input is prefered over "defaults":
23
+ # User input is preferred over "defaults":
22
24
  # user_options = { foo: "bar" }
23
25
  # default_options = { foo: "zoo" }
24
26
  # options = UserFileDefaultOptions.new(user_options, default_options)
@@ -30,7 +32,7 @@ module Puma
30
32
  # puts options.all_of(:foo)
31
33
  # # => ["bar", "zoo"]
32
34
  #
33
- # A "file" option can be set. This config will be prefered over "default" options
35
+ # A "file" option can be set. This config will be preferred over "default" options
34
36
  # but will defer to any available "user" specified options.
35
37
  #
36
38
  # user_options = { foo: "bar" }
@@ -52,9 +54,7 @@ module Puma
52
54
  attr_reader :user_options, :file_options, :default_options
53
55
 
54
56
  def [](key)
55
- return user_options[key] if user_options.key?(key)
56
- return file_options[key] if file_options.key?(key)
57
- return default_options[key] if default_options.key?(key)
57
+ fetch(key)
58
58
  end
59
59
 
60
60
  def []=(key, value)
@@ -62,7 +62,11 @@ module Puma
62
62
  end
63
63
 
64
64
  def fetch(key, default_value = nil)
65
- self[key] || default_value
65
+ return user_options[key] if user_options.key?(key)
66
+ return file_options[key] if file_options.key?(key)
67
+ return default_options[key] if default_options.key?(key)
68
+
69
+ default_value
66
70
  end
67
71
 
68
72
  def all_of(key)
@@ -88,6 +92,12 @@ module Puma
88
92
  end
89
93
  end
90
94
  end
95
+
96
+ def final_options
97
+ default_options
98
+ .merge(file_options)
99
+ .merge(user_options)
100
+ end
91
101
  end
92
102
 
93
103
  # The main configuration class of Puma.
@@ -104,16 +114,17 @@ module Puma
104
114
  #
105
115
  # It also handles loading plugins.
106
116
  #
107
- # > Note: `:port` and `:host` are not valid keys. By they time they make it to the
117
+ # [Note:]
118
+ # `:port` and `:host` are not valid keys. By the time they make it to the
108
119
  # configuration options they are expected to be incorporated into a `:binds` key.
109
120
  # Under the hood the DSL maps `port` and `host` calls to `:binds`
110
121
  #
111
- # config = Configuration.new({}) do |user_config, file_config, default_config|
112
- # user_config.port 3003
113
- # end
114
- # config.load
115
- # puts config.options[:port]
116
- # # => 3003
122
+ # config = Configuration.new({}) do |user_config, file_config, default_config|
123
+ # user_config.port 3003
124
+ # end
125
+ # config.load
126
+ # puts config.options[:port]
127
+ # # => 3003
117
128
  #
118
129
  # It is expected that `load` is called on the configuration instance after setting
119
130
  # config. This method expands any values in `config_file` and puts them into the
@@ -135,6 +146,10 @@ module Puma
135
146
  @file_dsl = DSL.new(@options.file_options, self)
136
147
  @default_dsl = DSL.new(@options.default_options, self)
137
148
 
149
+ if !@options[:prune_bundler]
150
+ default_options[:preload_app] = (@options[:workers] > 1) && Puma.forkable?
151
+ end
152
+
138
153
  if block
139
154
  configure(&block)
140
155
  end
@@ -165,26 +180,35 @@ module Puma
165
180
  self
166
181
  end
167
182
 
183
+ # @version 5.0.0
184
+ def default_max_threads
185
+ Puma.mri? ? 5 : 16
186
+ end
187
+
168
188
  def puma_default_options
169
189
  {
170
- :min_threads => 0,
171
- :max_threads => 16,
190
+ :min_threads => Integer(ENV['PUMA_MIN_THREADS'] || ENV['MIN_THREADS'] || 0),
191
+ :max_threads => Integer(ENV['PUMA_MAX_THREADS'] || ENV['MAX_THREADS'] || default_max_threads),
172
192
  :log_requests => false,
173
193
  :debug => false,
174
194
  :binds => ["tcp://#{DefaultTCPHost}:#{DefaultTCPPort}"],
175
- :workers => 0,
176
- :daemon => false,
195
+ :workers => Integer(ENV['WEB_CONCURRENCY'] || 0),
196
+ :silence_single_worker_warning => false,
177
197
  :mode => :http,
178
198
  :worker_timeout => DefaultWorkerTimeout,
179
199
  :worker_boot_timeout => DefaultWorkerTimeout,
180
200
  :worker_shutdown_timeout => DefaultWorkerShutdownTimeout,
181
201
  :remote_address => :socket,
182
202
  :tag => method(:infer_tag),
183
- :environment => -> { ENV['RACK_ENV'] || "development" },
203
+ :environment => -> { ENV['RACK_ENV'] || ENV['RAILS_ENV'] || "development" },
184
204
  :rackup => DefaultRackup,
185
205
  :logger => STDOUT,
186
206
  :persistent_timeout => Const::PERSISTENT_TIMEOUT,
187
- :first_data_timeout => Const::FIRST_DATA_TIMEOUT
207
+ :first_data_timeout => Const::FIRST_DATA_TIMEOUT,
208
+ :raise_exception_on_sigterm => true,
209
+ :max_fast_inline => Const::MAX_FAST_INLINE,
210
+ :io_selector_backend => :auto,
211
+ :mutate_stdout_and_stderr_to_sync_on_write => true,
188
212
  }
189
213
  end
190
214
 
@@ -242,14 +266,6 @@ module Puma
242
266
  def app
243
267
  found = options[:app] || load_rackup
244
268
 
245
- if @options[:mode] == :tcp
246
- require 'puma/tcp_logger'
247
-
248
- logger = @options[:logger]
249
- quiet = !@options[:log_requests]
250
- return TCPLogger.new(logger, found, quiet)
251
- end
252
-
253
269
  if @options[:log_requests]
254
270
  require 'puma/commonlogger'
255
271
  logger = @options[:logger]
@@ -272,8 +288,19 @@ module Puma
272
288
  @plugins.create name
273
289
  end
274
290
 
275
- def run_hooks(key, arg)
276
- @options.all_of(key).each { |b| b.call arg }
291
+ def run_hooks(key, arg, events)
292
+ @options.all_of(key).each do |b|
293
+ begin
294
+ b.call arg
295
+ rescue => e
296
+ events.log "WARNING hook #{key} failed with exception (#{e.class}) #{e.message}"
297
+ events.debug e.backtrace.join("\n")
298
+ end
299
+ end
300
+ end
301
+
302
+ def final_options
303
+ @options.final_options
277
304
  end
278
305
 
279
306
  def self.temp_path
@@ -329,29 +356,9 @@ module Puma
329
356
  end
330
357
 
331
358
  def self.random_token
332
- begin
333
- require 'openssl'
334
- rescue LoadError
335
- end
336
-
337
- count = 16
338
-
339
- bytes = nil
340
-
341
- if defined? OpenSSL::Random
342
- bytes = OpenSSL::Random.random_bytes(count)
343
- elsif File.exist?("/dev/urandom")
344
- File.open('/dev/urandom') { |f| bytes = f.read(count) }
345
- end
346
-
347
- if bytes
348
- token = "".dup
349
- bytes.each_byte { |b| token << b.to_s(16) }
350
- else
351
- token = (0..count).to_a.map { rand(255).to_s(16) }.join
352
- end
359
+ require 'securerandom' unless defined?(SecureRandom)
353
360
 
354
- return token
361
+ SecureRandom.hex(16)
355
362
  end
356
363
  end
357
364
  end
data/lib/puma/const.rb CHANGED
@@ -1,4 +1,6 @@
1
1
  #encoding: utf-8
2
+ # frozen_string_literal: true
3
+
2
4
  module Puma
3
5
  class UnsupportedOption < RuntimeError
4
6
  end
@@ -98,8 +100,9 @@ module Puma
98
100
  # too taxing on performance.
99
101
  module Const
100
102
 
101
- PUMA_VERSION = VERSION = "3.12.0".freeze
102
- CODE_NAME = "Llamas in Pajamas".freeze
103
+ PUMA_VERSION = VERSION = "5.3.1".freeze
104
+ CODE_NAME = "Sweetnighter".freeze
105
+
103
106
  PUMA_SERVER_STRING = ['puma', PUMA_VERSION, CODE_NAME].join(' ').freeze
104
107
 
105
108
  FAST_TRACK_KA_TIMEOUT = 0.2
@@ -116,31 +119,35 @@ module Puma
116
119
  # sending data back
117
120
  WRITE_TIMEOUT = 10
118
121
 
122
+ # How many requests to attempt inline before sending a client back to
123
+ # the reactor to be subject to normal ordering. The idea here is that
124
+ # we amortize the cost of going back to the reactor for a well behaved
125
+ # but very "greedy" client across 10 requests. This prevents a not
126
+ # well behaved client from monopolizing the thread forever.
127
+ MAX_FAST_INLINE = 10
128
+
119
129
  # The original URI requested by the client.
120
130
  REQUEST_URI= 'REQUEST_URI'.freeze
121
131
  REQUEST_PATH = 'REQUEST_PATH'.freeze
122
132
  QUERY_STRING = 'QUERY_STRING'.freeze
133
+ CONTENT_LENGTH = "CONTENT_LENGTH".freeze
123
134
 
124
135
  PATH_INFO = 'PATH_INFO'.freeze
125
136
 
126
137
  PUMA_TMP_BASE = "puma".freeze
127
138
 
128
- # Indicate that we couldn't parse the request
129
- ERROR_400_RESPONSE = "HTTP/1.1 400 Bad Request\r\n\r\n".freeze
130
-
131
- # The standard empty 404 response for bad requests. Use Error4040Handler for custom stuff.
132
- ERROR_404_RESPONSE = "HTTP/1.1 404 Not Found\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\nNOT FOUND".freeze
133
-
134
- # The standard empty 408 response for requests that timed out.
135
- ERROR_408_RESPONSE = "HTTP/1.1 408 Request Timeout\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\n".freeze
136
-
137
- CONTENT_LENGTH = "CONTENT_LENGTH".freeze
138
-
139
- # Indicate that there was an internal error, obviously.
140
- ERROR_500_RESPONSE = "HTTP/1.1 500 Internal Server Error\r\n\r\n".freeze
141
-
142
- # A common header for indicating the server is too busy. Not used yet.
143
- ERROR_503_RESPONSE = "HTTP/1.1 503 Service Unavailable\r\n\r\nBUSY".freeze
139
+ ERROR_RESPONSE = {
140
+ # Indicate that we couldn't parse the request
141
+ 400 => "HTTP/1.1 400 Bad Request\r\n\r\n".freeze,
142
+ # The standard empty 404 response for bad requests. Use Error4040Handler for custom stuff.
143
+ 404 => "HTTP/1.1 404 Not Found\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\nNOT FOUND".freeze,
144
+ # The standard empty 408 response for requests that timed out.
145
+ 408 => "HTTP/1.1 408 Request Timeout\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\n".freeze,
146
+ # Indicate that there was an internal error, obviously.
147
+ 500 => "HTTP/1.1 500 Internal Server Error\r\n\r\n".freeze,
148
+ # A common header for indicating the server is too busy. Not used yet.
149
+ 503 => "HTTP/1.1 503 Service Unavailable\r\n\r\nBUSY".freeze
150
+ }
144
151
 
145
152
  # The basic max request size we'll try to read.
146
153
  CHUNK_SIZE = 16 * 1024
@@ -158,6 +165,9 @@ module Puma
158
165
  LINE_END = "\r\n".freeze
159
166
  REMOTE_ADDR = "REMOTE_ADDR".freeze
160
167
  HTTP_X_FORWARDED_FOR = "HTTP_X_FORWARDED_FOR".freeze
168
+ HTTP_X_FORWARDED_SSL = "HTTP_X_FORWARDED_SSL".freeze
169
+ HTTP_X_FORWARDED_SCHEME = "HTTP_X_FORWARDED_SCHEME".freeze
170
+ HTTP_X_FORWARDED_PROTO = "HTTP_X_FORWARDED_PROTO".freeze
161
171
 
162
172
  SERVER_NAME = "SERVER_NAME".freeze
163
173
  SERVER_PORT = "SERVER_PORT".freeze
@@ -166,7 +176,6 @@ module Puma
166
176
  PORT_443 = "443".freeze
167
177
  LOCALHOST = "localhost".freeze
168
178
  LOCALHOST_IP = "127.0.0.1".freeze
169
- LOCALHOST_ADDR = "127.0.0.1:0".freeze
170
179
 
171
180
  SERVER_PROTOCOL = "SERVER_PROTOCOL".freeze
172
181
  HTTP_11 = "HTTP/1.1".freeze
@@ -225,5 +234,18 @@ module Puma
225
234
  HIJACK_IO = "rack.hijack_io".freeze
226
235
 
227
236
  EARLY_HINTS = "rack.early_hints".freeze
237
+
238
+ # Minimum interval to checks worker health
239
+ WORKER_CHECK_INTERVAL = 5
240
+
241
+ # Illegal character in the key or value of response header
242
+ DQUOTE = "\"".freeze
243
+ HTTP_HEADER_DELIMITER = Regexp.escape("(),/:;<=>?@[]{}\\").freeze
244
+ ILLEGAL_HEADER_KEY_REGEX = /[\x00-\x20#{DQUOTE}#{HTTP_HEADER_DELIMITER}]/.freeze
245
+ # header values can contain HTAB?
246
+ ILLEGAL_HEADER_VALUE_REGEX = /[\x00-\x08\x0A-\x1F]/.freeze
247
+
248
+ # Banned keys of response header
249
+ BANNED_HEADER_KEY = /\A(rack\.|status\z)/.freeze
228
250
  end
229
251
  end
@@ -1,15 +1,42 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'optparse'
2
- require 'puma/state_file'
3
- require 'puma/const'
4
- require 'puma/detect'
5
- require 'puma/configuration'
4
+ require_relative 'state_file'
5
+ require_relative 'const'
6
+ require_relative 'detect'
7
+ require_relative 'configuration'
6
8
  require 'uri'
7
9
  require 'socket'
8
10
 
9
11
  module Puma
10
12
  class ControlCLI
11
13
 
12
- COMMANDS = %w{halt restart phased-restart start stats status stop reload-worker-directory gc gc-stats}
14
+ # values must be string or nil
15
+ # value of `nil` means command cannot be processed via signal
16
+ # @version 5.0.3
17
+ CMD_PATH_SIG_MAP = {
18
+ 'gc' => nil,
19
+ 'gc-stats' => nil,
20
+ 'halt' => 'SIGQUIT',
21
+ 'phased-restart' => 'SIGUSR1',
22
+ 'refork' => 'SIGURG',
23
+ 'reload-worker-directory' => nil,
24
+ 'restart' => 'SIGUSR2',
25
+ 'start' => nil,
26
+ 'stats' => nil,
27
+ 'status' => '',
28
+ 'stop' => 'SIGTERM',
29
+ 'thread-backtraces' => nil
30
+ }.freeze
31
+
32
+ # @deprecated 6.0.0
33
+ COMMANDS = CMD_PATH_SIG_MAP.keys.freeze
34
+
35
+ # commands that cannot be used in a request
36
+ NO_REQ_COMMANDS = %w{refork}.freeze
37
+
38
+ # @version 5.0.0
39
+ PRINTABLE_COMMANDS = %w{gc-stats stats thread-backtraces}.freeze
13
40
 
14
41
  def initialize(argv, stdout=STDOUT, stderr=STDERR)
15
42
  @state = nil
@@ -20,6 +47,7 @@ module Puma
20
47
  @control_auth_token = nil
21
48
  @config_file = nil
22
49
  @command = nil
50
+ @environment = ENV['RACK_ENV'] || ENV['RAILS_ENV']
23
51
 
24
52
  @argv = argv.dup
25
53
  @stdout = stdout
@@ -27,7 +55,7 @@ module Puma
27
55
  @cli_options = {}
28
56
 
29
57
  opts = OptionParser.new do |o|
30
- o.banner = "Usage: pumactl (-p PID | -P pidfile | -S status_file | -C url -T token | -F config.rb) (#{COMMANDS.join("|")})"
58
+ o.banner = "Usage: pumactl (-p PID | -P pidfile | -S status_file | -C url -T token | -F config.rb) (#{CMD_PATH_SIG_MAP.keys.join("|")})"
31
59
 
32
60
  o.on "-S", "--state PATH", "Where the state file to use is" do |arg|
33
61
  @state = arg
@@ -57,13 +85,18 @@ module Puma
57
85
  @config_file = arg
58
86
  end
59
87
 
88
+ o.on "-e", "--environment ENVIRONMENT",
89
+ "The environment to run the Rack app on (default development)" do |arg|
90
+ @environment = arg
91
+ end
92
+
60
93
  o.on_tail("-H", "--help", "Show this message") do
61
94
  @stdout.puts o
62
95
  exit
63
96
  end
64
97
 
65
98
  o.on_tail("-V", "--version", "Show version") do
66
- puts Const::PUMA_VERSION
99
+ @stdout.puts Const::PUMA_VERSION
67
100
  exit
68
101
  end
69
102
  end
@@ -73,9 +106,22 @@ module Puma
73
106
 
74
107
  @command = argv.shift
75
108
 
109
+ # check presence of command
110
+ unless @command
111
+ raise "Available commands: #{CMD_PATH_SIG_MAP.keys.join(", ")}"
112
+ end
113
+
114
+ unless CMD_PATH_SIG_MAP.key? @command
115
+ raise "Invalid command: #{@command}"
116
+ end
117
+
76
118
  unless @config_file == '-'
77
- if @config_file.nil? and File.exist?('config/puma.rb')
78
- @config_file = 'config/puma.rb'
119
+ environment = @environment || 'development'
120
+
121
+ if @config_file.nil?
122
+ @config_file = %W(config/puma/#{environment}.rb config/puma.rb).find do |f|
123
+ File.exist?(f)
124
+ end
79
125
  end
80
126
 
81
127
  if @config_file
@@ -87,19 +133,8 @@ module Puma
87
133
  @pidfile ||= config.options[:pidfile]
88
134
  end
89
135
  end
90
-
91
- # check present of command
92
- unless @command
93
- raise "Available commands: #{COMMANDS.join(", ")}"
94
- end
95
-
96
- unless COMMANDS.include? @command
97
- raise "Invalid command: #{@command}"
98
- end
99
-
100
136
  rescue => e
101
137
  @stdout.puts e.message
102
- @stdout.puts e.backtrace
103
138
  exit 1
104
139
  end
105
140
 
@@ -121,7 +156,7 @@ module Puma
121
156
  @pid = sf.pid
122
157
  elsif @pidfile
123
158
  # get pid from pid_file
124
- @pid = File.open(@pidfile).gets.to_i
159
+ @pid = File.read(@pidfile, mode: 'rb:UTF-8').to_i
125
160
  end
126
161
  end
127
162
 
@@ -129,17 +164,29 @@ module Puma
129
164
  uri = URI.parse @control_url
130
165
 
131
166
  # create server object by scheme
132
- @server = case uri.scheme
133
- when "tcp"
134
- TCPSocket.new uri.host, uri.port
135
- when "unix"
136
- UNIXSocket.new "#{uri.host}#{uri.path}"
137
- else
138
- raise "Invalid scheme: #{uri.scheme}"
139
- end
140
-
141
- if @command == "status"
142
- message "Puma is started"
167
+ server =
168
+ case uri.scheme
169
+ when 'ssl'
170
+ require 'openssl'
171
+ OpenSSL::SSL::SSLSocket.new(
172
+ TCPSocket.new(uri.host, uri.port),
173
+ OpenSSL::SSL::SSLContext.new)
174
+ .tap { |ssl| ssl.sync_close = true } # default is false
175
+ .tap(&:connect)
176
+ when 'tcp'
177
+ TCPSocket.new uri.host, uri.port
178
+ when 'unix'
179
+ # check for abstract UNIXSocket
180
+ UNIXSocket.new(@control_url.start_with?('unix://@') ?
181
+ "\0#{uri.host}#{uri.path}" : "#{uri.host}#{uri.path}")
182
+ else
183
+ raise "Invalid scheme: #{uri.scheme}"
184
+ end
185
+
186
+ if @command == 'status'
187
+ message 'Puma is started'
188
+ elsif NO_REQ_COMMANDS.include? @command
189
+ raise "Invalid request command: #{@command}"
143
190
  else
144
191
  url = "/#{@command}"
145
192
 
@@ -147,10 +194,10 @@ module Puma
147
194
  url = url + "?token=#{@control_auth_token}"
148
195
  end
149
196
 
150
- @server << "GET #{url} HTTP/1.0\r\n\r\n"
197
+ server.syswrite "GET #{url} HTTP/1.0\r\n\r\n"
151
198
 
152
- unless data = @server.read
153
- raise "Server closed connection before responding"
199
+ unless data = server.read
200
+ raise 'Server closed connection before responding'
154
201
  end
155
202
 
156
203
  response = data.split("\r\n")
@@ -159,57 +206,55 @@ module Puma
159
206
  raise "Server sent empty response"
160
207
  end
161
208
 
162
- (@http,@code,@message) = response.first.split(" ",3)
209
+ @http, @code, @message = response.first.split(' ',3)
163
210
 
164
- if @code == "403"
165
- raise "Unauthorized access to server (wrong auth token)"
166
- elsif @code == "404"
211
+ if @code == '403'
212
+ raise 'Unauthorized access to server (wrong auth token)'
213
+ elsif @code == '404'
167
214
  raise "Command error: #{response.last}"
168
- elsif @code != "200"
215
+ elsif @code != '200'
169
216
  raise "Bad response from server: #{@code}"
170
217
  end
171
218
 
172
219
  message "Command #{@command} sent success"
173
- message response.last if @command == "stats" || @command == "gc-stats"
220
+ message response.last if PRINTABLE_COMMANDS.include?(@command)
221
+ end
222
+ ensure
223
+ if server
224
+ if uri.scheme == 'ssl'
225
+ server.sysclose
226
+ else
227
+ server.close unless server.closed?
228
+ end
174
229
  end
175
-
176
- @server.close
177
230
  end
178
231
 
179
232
  def send_signal
180
233
  unless @pid
181
- raise "Neither pid nor control url available"
234
+ raise 'Neither pid nor control url available'
182
235
  end
183
236
 
184
237
  begin
238
+ sig = CMD_PATH_SIG_MAP[@command]
185
239
 
186
- case @command
187
- when "restart"
188
- Process.kill "SIGUSR2", @pid
189
-
190
- when "halt"
191
- Process.kill "QUIT", @pid
192
-
193
- when "stop"
194
- Process.kill "SIGTERM", @pid
195
-
196
- when "stats"
197
- puts "Stats not available via pid only"
198
- return
199
-
200
- when "reload-worker-directory"
201
- puts "reload-worker-directory not available via pid only"
240
+ if sig.nil?
241
+ @stdout.puts "'#{@command}' not available via pid only"
242
+ @stdout.flush unless @stdout.sync
202
243
  return
203
-
204
- when "phased-restart"
205
- Process.kill "SIGUSR1", @pid
206
-
207
- else
244
+ elsif sig.start_with? 'SIG'
245
+ Process.kill sig, @pid
246
+ elsif @command == 'status'
247
+ begin
248
+ Process.kill 0, @pid
249
+ @stdout.puts 'Puma is started'
250
+ @stdout.flush unless @stdout.sync
251
+ rescue Errno::ESRCH
252
+ raise 'Puma is not running'
253
+ end
208
254
  return
209
255
  end
210
-
211
256
  rescue SystemCallError
212
- if @command == "restart"
257
+ if @command == 'restart'
213
258
  start
214
259
  else
215
260
  raise "No pid '#{@pid}' found"
@@ -220,23 +265,21 @@ module Puma
220
265
  end
221
266
 
222
267
  def run
223
- return start if @command == "start"
224
-
268
+ return start if @command == 'start'
225
269
  prepare_configuration
226
270
 
227
- if Puma.windows?
271
+ if Puma.windows? || @control_url
228
272
  send_request
229
273
  else
230
- @control_url ? send_request : send_signal
274
+ send_signal
231
275
  end
232
276
 
233
277
  rescue => e
234
278
  message e.message
235
- message e.backtrace
236
279
  exit 1
237
280
  end
238
281
 
239
- private
282
+ private
240
283
  def start
241
284
  require 'puma/cli'
242
285
 
@@ -248,6 +291,7 @@ module Puma
248
291
  run_args += ["--control-url", @control_url] if @control_url
249
292
  run_args += ["--control-token", @control_auth_token] if @control_auth_token
250
293
  run_args += ["-C", @config_file] if @config_file
294
+ run_args += ["-e", @environment] if @environment
251
295
 
252
296
  events = Puma::Events.new @stdout, @stderr
253
297
 
data/lib/puma/detect.rb CHANGED
@@ -1,13 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file can be loaded independently of puma.rb, so it cannot have any code
4
+ # that assumes puma.rb is loaded.
5
+
6
+
1
7
  module Puma
2
- IS_JRUBY = defined?(JRUBY_VERSION)
8
+ # @version 5.2.1
9
+ HAS_FORK = ::Process.respond_to? :fork
10
+
11
+ IS_JRUBY = Object.const_defined? :JRUBY_VERSION
12
+
13
+ IS_WINDOWS = !!(RUBY_PLATFORM =~ /mswin|ming|cygwin/ ||
14
+ IS_JRUBY && RUBY_DESCRIPTION =~ /mswin/)
15
+
16
+ # @version 5.2.0
17
+ IS_MRI = (RUBY_ENGINE == 'ruby' || RUBY_ENGINE.nil?)
3
18
 
4
19
  def self.jruby?
5
20
  IS_JRUBY
6
21
  end
7
22
 
8
- IS_WINDOWS = RUBY_PLATFORM =~ /mswin|ming|cygwin/
9
-
10
23
  def self.windows?
11
24
  IS_WINDOWS
12
25
  end
26
+
27
+ # @version 5.0.0
28
+ def self.mri?
29
+ IS_MRI
30
+ end
31
+
32
+ # @version 5.0.0
33
+ def self.forkable?
34
+ HAS_FORK
35
+ end
13
36
  end