puma 3.12.1 → 5.6.4

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 (96) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +1553 -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/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/{tools → docs}/jungle/rc.d/README.md +1 -1
  15. data/{tools → docs}/jungle/rc.d/puma +2 -2
  16. data/{tools → docs}/jungle/rc.d/puma.conf +0 -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 +95 -120
  25. data/ext/puma_http11/PumaHttp11Service.java +2 -2
  26. data/ext/puma_http11/ext_help.h +1 -1
  27. data/ext/puma_http11/extconf.rb +51 -1
  28. data/ext/puma_http11/http11_parser.c +105 -117
  29. data/ext/puma_http11/http11_parser.h +1 -1
  30. data/ext/puma_http11/http11_parser.java.rl +22 -38
  31. data/ext/puma_http11/http11_parser.rl +4 -2
  32. data/ext/puma_http11/http11_parser_common.rl +4 -4
  33. data/ext/puma_http11/mini_ssl.c +319 -96
  34. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  35. data/ext/puma_http11/org/jruby/puma/Http11.java +108 -116
  36. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +84 -99
  37. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +120 -65
  38. data/ext/puma_http11/puma_http11.c +35 -51
  39. data/lib/puma/app/status.rb +68 -49
  40. data/lib/puma/binder.rb +234 -137
  41. data/lib/puma/cli.rb +28 -18
  42. data/lib/puma/client.rb +343 -230
  43. data/lib/puma/cluster/worker.rb +173 -0
  44. data/lib/puma/cluster/worker_handle.rb +94 -0
  45. data/lib/puma/cluster.rb +247 -232
  46. data/lib/puma/commonlogger.rb +2 -2
  47. data/lib/puma/configuration.rb +61 -51
  48. data/lib/puma/const.rb +42 -21
  49. data/lib/puma/control_cli.rb +109 -67
  50. data/lib/puma/detect.rb +29 -2
  51. data/lib/puma/dsl.rb +615 -123
  52. data/lib/puma/error_logger.rb +104 -0
  53. data/lib/puma/events.rb +55 -31
  54. data/lib/puma/io_buffer.rb +7 -5
  55. data/lib/puma/jruby_restart.rb +0 -58
  56. data/lib/puma/json_serialization.rb +96 -0
  57. data/lib/puma/launcher.rb +182 -69
  58. data/lib/puma/minissl/context_builder.rb +81 -0
  59. data/lib/puma/minissl.rb +161 -61
  60. data/lib/puma/null_io.rb +13 -1
  61. data/lib/puma/plugin/tmp_restart.rb +2 -0
  62. data/lib/puma/plugin.rb +7 -13
  63. data/lib/puma/queue_close.rb +26 -0
  64. data/lib/puma/rack/builder.rb +3 -5
  65. data/lib/puma/rack/urlmap.rb +2 -0
  66. data/lib/puma/rack_default.rb +2 -0
  67. data/lib/puma/reactor.rb +85 -316
  68. data/lib/puma/request.rb +472 -0
  69. data/lib/puma/runner.rb +48 -55
  70. data/lib/puma/server.rb +303 -695
  71. data/lib/puma/single.rb +11 -67
  72. data/lib/puma/state_file.rb +47 -8
  73. data/lib/puma/systemd.rb +46 -0
  74. data/lib/puma/thread_pool.rb +132 -82
  75. data/lib/puma/util.rb +21 -7
  76. data/lib/puma.rb +54 -0
  77. data/lib/rack/handler/puma.rb +5 -6
  78. data/tools/Dockerfile +16 -0
  79. data/tools/trickletest.rb +0 -1
  80. metadata +45 -29
  81. data/ext/puma_http11/io_buffer.c +0 -155
  82. data/lib/puma/accept_nonblock.rb +0 -23
  83. data/lib/puma/compat.rb +0 -14
  84. data/lib/puma/convenient.rb +0 -25
  85. data/lib/puma/daemon_ext.rb +0 -33
  86. data/lib/puma/delegation.rb +0 -13
  87. data/lib/puma/java_io_buffer.rb +0 -47
  88. data/lib/puma/rack/backports/uri/common_193.rb +0 -33
  89. data/lib/puma/tcp_logger.rb +0 -41
  90. data/tools/jungle/README.md +0 -19
  91. data/tools/jungle/init.d/README.md +0 -61
  92. data/tools/jungle/init.d/puma +0 -421
  93. data/tools/jungle/init.d/run-puma +0 -18
  94. data/tools/jungle/upstart/README.md +0 -61
  95. data/tools/jungle/upstart/puma-manager.conf +0 -31
  96. data/tools/jungle/upstart/puma.conf +0 -69
@@ -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.4".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,32 @@ 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
+ '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
15
40
 
16
41
  def initialize(argv, stdout=STDOUT, stderr=STDERR)
17
42
  @state = nil
@@ -22,6 +47,7 @@ module Puma
22
47
  @control_auth_token = nil
23
48
  @config_file = nil
24
49
  @command = nil
50
+ @environment = ENV['APP_ENV'] || ENV['RACK_ENV'] || ENV['RAILS_ENV']
25
51
 
26
52
  @argv = argv.dup
27
53
  @stdout = stdout
@@ -29,7 +55,7 @@ module Puma
29
55
  @cli_options = {}
30
56
 
31
57
  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("|")})"
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("|")})"
33
59
 
34
60
  o.on "-S", "--state PATH", "Where the state file to use is" do |arg|
35
61
  @state = arg
@@ -59,13 +85,18 @@ module Puma
59
85
  @config_file = arg
60
86
  end
61
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
+
62
93
  o.on_tail("-H", "--help", "Show this message") do
63
94
  @stdout.puts o
64
95
  exit
65
96
  end
66
97
 
67
98
  o.on_tail("-V", "--version", "Show version") do
68
- puts Const::PUMA_VERSION
99
+ @stdout.puts Const::PUMA_VERSION
69
100
  exit
70
101
  end
71
102
  end
@@ -75,9 +106,22 @@ module Puma
75
106
 
76
107
  @command = argv.shift
77
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
+
78
118
  unless @config_file == '-'
79
- if @config_file.nil? and File.exist?('config/puma.rb')
80
- @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
81
125
  end
82
126
 
83
127
  if @config_file
@@ -89,19 +133,8 @@ module Puma
89
133
  @pidfile ||= config.options[:pidfile]
90
134
  end
91
135
  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
136
  rescue => e
103
137
  @stdout.puts e.message
104
- @stdout.puts e.backtrace
105
138
  exit 1
106
139
  end
107
140
 
@@ -123,7 +156,7 @@ module Puma
123
156
  @pid = sf.pid
124
157
  elsif @pidfile
125
158
  # get pid from pid_file
126
- @pid = File.open(@pidfile).gets.to_i
159
+ @pid = File.read(@pidfile, mode: 'rb:UTF-8').to_i
127
160
  end
128
161
  end
129
162
 
@@ -131,17 +164,29 @@ module Puma
131
164
  uri = URI.parse @control_url
132
165
 
133
166
  # 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"
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}"
145
190
  else
146
191
  url = "/#{@command}"
147
192
 
@@ -149,10 +194,10 @@ module Puma
149
194
  url = url + "?token=#{@control_auth_token}"
150
195
  end
151
196
 
152
- server << "GET #{url} HTTP/1.0\r\n\r\n"
197
+ server.syswrite "GET #{url} HTTP/1.0\r\n\r\n"
153
198
 
154
199
  unless data = server.read
155
- raise "Server closed connection before responding"
200
+ raise 'Server closed connection before responding'
156
201
  end
157
202
 
158
203
  response = data.split("\r\n")
@@ -161,57 +206,55 @@ module Puma
161
206
  raise "Server sent empty response"
162
207
  end
163
208
 
164
- (@http,@code,@message) = response.first.split(" ",3)
209
+ @http, @code, @message = response.first.split(' ',3)
165
210
 
166
- if @code == "403"
167
- raise "Unauthorized access to server (wrong auth token)"
168
- elsif @code == "404"
211
+ if @code == '403'
212
+ raise 'Unauthorized access to server (wrong auth token)'
213
+ elsif @code == '404'
169
214
  raise "Command error: #{response.last}"
170
- elsif @code != "200"
215
+ elsif @code != '200'
171
216
  raise "Bad response from server: #{@code}"
172
217
  end
173
218
 
174
219
  message "Command #{@command} sent success"
175
- message response.last if @command == "stats" || @command == "gc-stats"
220
+ message response.last if PRINTABLE_COMMANDS.include?(@command)
176
221
  end
177
222
  ensure
178
- server.close if server && !server.closed?
223
+ if server
224
+ if uri.scheme == 'ssl'
225
+ server.sysclose
226
+ else
227
+ server.close unless server.closed?
228
+ end
229
+ end
179
230
  end
180
231
 
181
232
  def send_signal
182
233
  unless @pid
183
- raise "Neither pid nor control url available"
234
+ raise 'Neither pid nor control url available'
184
235
  end
185
236
 
186
237
  begin
238
+ sig = CMD_PATH_SIG_MAP[@command]
187
239
 
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"
240
+ if sig.nil?
241
+ @stdout.puts "'#{@command}' not available via pid only"
242
+ @stdout.flush unless @stdout.sync
204
243
  return
205
-
206
- when "phased-restart"
207
- Process.kill "SIGUSR1", @pid
208
-
209
- 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
210
254
  return
211
255
  end
212
-
213
256
  rescue SystemCallError
214
- if @command == "restart"
257
+ if @command == 'restart'
215
258
  start
216
259
  else
217
260
  raise "No pid '#{@pid}' found"
@@ -222,23 +265,21 @@ module Puma
222
265
  end
223
266
 
224
267
  def run
225
- return start if @command == "start"
226
-
268
+ return start if @command == 'start'
227
269
  prepare_configuration
228
270
 
229
- if Puma.windows?
271
+ if Puma.windows? || @control_url
230
272
  send_request
231
273
  else
232
- @control_url ? send_request : send_signal
274
+ send_signal
233
275
  end
234
276
 
235
277
  rescue => e
236
278
  message e.message
237
- message e.backtrace
238
279
  exit 1
239
280
  end
240
281
 
241
- private
282
+ private
242
283
  def start
243
284
  require 'puma/cli'
244
285
 
@@ -250,6 +291,7 @@ module Puma
250
291
  run_args += ["--control-url", @control_url] if @control_url
251
292
  run_args += ["--control-token", @control_auth_token] if @control_auth_token
252
293
  run_args += ["-C", @config_file] if @config_file
294
+ run_args += ["-e", @environment] if @environment
253
295
 
254
296
  events = Puma::Events.new @stdout, @stderr
255
297
 
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