puma 3.12.1 → 5.6.7

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