puma 4.3.12 → 6.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 (96) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +1729 -521
  3. data/LICENSE +23 -20
  4. data/README.md +169 -45
  5. data/bin/puma-wild +3 -9
  6. data/docs/architecture.md +63 -26
  7. data/docs/compile_options.md +55 -0
  8. data/docs/deployment.md +60 -69
  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/{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 +2 -2
  19. data/docs/plugins.md +15 -15
  20. data/docs/rails_dev_mode.md +28 -0
  21. data/docs/restart.md +46 -23
  22. data/docs/signals.md +13 -11
  23. data/docs/stats.md +142 -0
  24. data/docs/systemd.md +84 -128
  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 -4
  28. data/ext/puma_http11/ext_help.h +1 -1
  29. data/ext/puma_http11/extconf.rb +49 -12
  30. data/ext/puma_http11/http11_parser.c +46 -48
  31. data/ext/puma_http11/http11_parser.h +2 -2
  32. data/ext/puma_http11/http11_parser.java.rl +3 -3
  33. data/ext/puma_http11/http11_parser.rl +3 -3
  34. data/ext/puma_http11/http11_parser_common.rl +2 -2
  35. data/ext/puma_http11/mini_ssl.c +278 -93
  36. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  37. data/ext/puma_http11/org/jruby/puma/Http11.java +6 -6
  38. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +4 -6
  39. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +241 -96
  40. data/ext/puma_http11/puma_http11.c +46 -57
  41. data/lib/puma/app/status.rb +53 -39
  42. data/lib/puma/binder.rb +237 -121
  43. data/lib/puma/cli.rb +34 -34
  44. data/lib/puma/client.rb +172 -98
  45. data/lib/puma/cluster/worker.rb +180 -0
  46. data/lib/puma/cluster/worker_handle.rb +97 -0
  47. data/lib/puma/cluster.rb +226 -231
  48. data/lib/puma/commonlogger.rb +21 -14
  49. data/lib/puma/configuration.rb +114 -87
  50. data/lib/puma/const.rb +139 -95
  51. data/lib/puma/control_cli.rb +99 -79
  52. data/lib/puma/detect.rb +33 -2
  53. data/lib/puma/dsl.rb +516 -110
  54. data/lib/puma/error_logger.rb +113 -0
  55. data/lib/puma/events.rb +16 -115
  56. data/lib/puma/io_buffer.rb +44 -2
  57. data/lib/puma/jruby_restart.rb +2 -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 +164 -155
  61. data/lib/puma/log_writer.rb +147 -0
  62. data/lib/puma/minissl/context_builder.rb +36 -19
  63. data/lib/puma/minissl.rb +230 -55
  64. data/lib/puma/null_io.rb +18 -1
  65. data/lib/puma/plugin/systemd.rb +90 -0
  66. data/lib/puma/plugin/tmp_restart.rb +1 -1
  67. data/lib/puma/plugin.rb +3 -12
  68. data/lib/puma/rack/builder.rb +7 -11
  69. data/lib/puma/rack/urlmap.rb +0 -0
  70. data/lib/puma/rack_default.rb +19 -4
  71. data/lib/puma/reactor.rb +93 -368
  72. data/lib/puma/request.rb +671 -0
  73. data/lib/puma/runner.rb +92 -75
  74. data/lib/puma/sd_notify.rb +149 -0
  75. data/lib/puma/server.rb +321 -794
  76. data/lib/puma/single.rb +20 -74
  77. data/lib/puma/state_file.rb +45 -8
  78. data/lib/puma/thread_pool.rb +140 -68
  79. data/lib/puma/util.rb +21 -4
  80. data/lib/puma.rb +54 -7
  81. data/lib/rack/handler/puma.rb +113 -87
  82. data/tools/{docker/Dockerfile → Dockerfile} +1 -1
  83. data/tools/trickletest.rb +0 -0
  84. metadata +33 -24
  85. data/docs/tcp_mode.md +0 -96
  86. data/ext/puma_http11/io_buffer.c +0 -155
  87. data/ext/puma_http11/org/jruby/puma/IOBuffer.java +0 -72
  88. data/lib/puma/accept_nonblock.rb +0 -29
  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
data/lib/puma/const.rb CHANGED
@@ -5,7 +5,6 @@ module Puma
5
5
  class UnsupportedOption < RuntimeError
6
6
  end
7
7
 
8
-
9
8
  # Every standard HTTP code mapped to the appropriate message. These are
10
9
  # used so frequently that they are placed directly in Puma for easy
11
10
  # access rather than Puma::Const itself.
@@ -19,6 +18,7 @@ module Puma
19
18
  100 => 'Continue',
20
19
  101 => 'Switching Protocols',
21
20
  102 => 'Processing',
21
+ 103 => 'Early Hints',
22
22
  200 => 'OK',
23
23
  201 => 'Created',
24
24
  202 => 'Accepted',
@@ -50,16 +50,16 @@ module Puma
50
50
  410 => 'Gone',
51
51
  411 => 'Length Required',
52
52
  412 => 'Precondition Failed',
53
- 413 => 'Payload Too Large',
53
+ 413 => 'Content Too Large',
54
54
  414 => 'URI Too Long',
55
55
  415 => 'Unsupported Media Type',
56
56
  416 => 'Range Not Satisfiable',
57
57
  417 => 'Expectation Failed',
58
- 418 => 'I\'m A Teapot',
59
58
  421 => 'Misdirected Request',
60
- 422 => 'Unprocessable Entity',
59
+ 422 => 'Unprocessable Content',
61
60
  423 => 'Locked',
62
61
  424 => 'Failed Dependency',
62
+ 425 => 'Too Early',
63
63
  426 => 'Upgrade Required',
64
64
  428 => 'Precondition Required',
65
65
  429 => 'Too Many Requests',
@@ -74,7 +74,7 @@ module Puma
74
74
  506 => 'Variant Also Negotiates',
75
75
  507 => 'Insufficient Storage',
76
76
  508 => 'Loop Detected',
77
- 510 => 'Not Extended',
77
+ 510 => 'Not Extended (OBSOLETED)',
78
78
  511 => 'Network Authentication Required'
79
79
  }.freeze
80
80
 
@@ -100,54 +100,40 @@ module Puma
100
100
  # too taxing on performance.
101
101
  module Const
102
102
 
103
- PUMA_VERSION = VERSION = "4.3.12".freeze
104
- CODE_NAME = "Mysterious Traveller".freeze
105
- PUMA_SERVER_STRING = ['puma', PUMA_VERSION, CODE_NAME].join(' ').freeze
106
-
107
- FAST_TRACK_KA_TIMEOUT = 0.2
103
+ PUMA_VERSION = VERSION = "6.3.1"
104
+ CODE_NAME = "Mugi No Toki Itaru"
108
105
 
109
- # The default number of seconds for another request within a persistent
110
- # session.
111
- PERSISTENT_TIMEOUT = 20
106
+ PUMA_SERVER_STRING = ["puma", PUMA_VERSION, CODE_NAME].join(" ").freeze
112
107
 
113
- # The default number of seconds to wait until we get the first data
114
- # for the request
115
- FIRST_DATA_TIMEOUT = 30
108
+ FAST_TRACK_KA_TIMEOUT = 0.2
116
109
 
117
110
  # How long to wait when getting some write blocking on the socket when
118
111
  # sending data back
119
112
  WRITE_TIMEOUT = 10
120
113
 
121
- # How many requests to attempt inline before sending a client back to
122
- # the reactor to be subject to normal ordering. The idea here is that
123
- # we amortize the cost of going back to the reactor for a well behaved
124
- # but very "greedy" client across 10 requests. This prevents a not
125
- # well behaved client from monopolizing the thread forever.
126
- MAX_FAST_INLINE = 10
127
-
128
114
  # The original URI requested by the client.
129
- REQUEST_URI= 'REQUEST_URI'.freeze
130
- REQUEST_PATH = 'REQUEST_PATH'.freeze
131
- QUERY_STRING = 'QUERY_STRING'.freeze
132
- CONTENT_LENGTH = "CONTENT_LENGTH".freeze
115
+ REQUEST_URI= "REQUEST_URI"
116
+ REQUEST_PATH = "REQUEST_PATH"
117
+ QUERY_STRING = "QUERY_STRING"
118
+ CONTENT_LENGTH = "CONTENT_LENGTH"
133
119
 
134
- PATH_INFO = 'PATH_INFO'.freeze
120
+ PATH_INFO = "PATH_INFO"
135
121
 
136
- PUMA_TMP_BASE = "puma".freeze
122
+ PUMA_TMP_BASE = "puma"
137
123
 
138
124
  ERROR_RESPONSE = {
139
125
  # Indicate that we couldn't parse the request
140
- 400 => "HTTP/1.1 400 Bad Request\r\n\r\n".freeze,
126
+ 400 => "HTTP/1.1 400 Bad Request\r\n\r\n",
141
127
  # The standard empty 404 response for bad requests. Use Error4040Handler for custom stuff.
142
- 404 => "HTTP/1.1 404 Not Found\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\nNOT FOUND".freeze,
128
+ 404 => "HTTP/1.1 404 Not Found\r\nConnection: close\r\n\r\n",
143
129
  # The standard empty 408 response for requests that timed out.
144
- 408 => "HTTP/1.1 408 Request Timeout\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\n".freeze,
130
+ 408 => "HTTP/1.1 408 Request Timeout\r\nConnection: close\r\n\r\n",
145
131
  # Indicate that there was an internal error, obviously.
146
- 500 => "HTTP/1.1 500 Internal Server Error\r\n\r\n".freeze,
132
+ 500 => "HTTP/1.1 500 Internal Server Error\r\n\r\n",
147
133
  # Incorrect or invalid header value
148
- 501 => "HTTP/1.1 501 Not Implemented\r\n\r\n".freeze,
134
+ 501 => "HTTP/1.1 501 Not Implemented\r\n\r\n",
149
135
  # A common header for indicating the server is too busy. Not used yet.
150
- 503 => "HTTP/1.1 503 Service Unavailable\r\n\r\nBUSY".freeze
136
+ 503 => "HTTP/1.1 503 Service Unavailable\r\n\r\n"
151
137
  }.freeze
152
138
 
153
139
  # The basic max request size we'll try to read.
@@ -160,86 +146,144 @@ module Puma
160
146
  # Maximum request body size before it is moved out of memory and into a tempfile for reading.
161
147
  MAX_BODY = MAX_HEADER
162
148
 
163
- REQUEST_METHOD = "REQUEST_METHOD".freeze
164
- HEAD = "HEAD".freeze
149
+ REQUEST_METHOD = "REQUEST_METHOD"
150
+ HEAD = "HEAD"
151
+
152
+ # based on https://www.rfc-editor.org/rfc/rfc9110.html#name-overview,
153
+ # with CONNECT removed, and PATCH added
154
+ SUPPORTED_HTTP_METHODS = %w[HEAD GET POST PUT DELETE OPTIONS TRACE PATCH].freeze
155
+
156
+ # list from https://www.iana.org/assignments/http-methods/http-methods.xhtml
157
+ # as of 04-May-23
158
+ IANA_HTTP_METHODS = %w[
159
+ ACL
160
+ BASELINE-CONTROL
161
+ BIND
162
+ CHECKIN
163
+ CHECKOUT
164
+ CONNECT
165
+ COPY
166
+ DELETE
167
+ GET
168
+ HEAD
169
+ LABEL
170
+ LINK
171
+ LOCK
172
+ MERGE
173
+ MKACTIVITY
174
+ MKCALENDAR
175
+ MKCOL
176
+ MKREDIRECTREF
177
+ MKWORKSPACE
178
+ MOVE
179
+ OPTIONS
180
+ ORDERPATCH
181
+ PATCH
182
+ POST
183
+ PRI
184
+ PROPFIND
185
+ PROPPATCH
186
+ PUT
187
+ REBIND
188
+ REPORT
189
+ SEARCH
190
+ TRACE
191
+ UNBIND
192
+ UNCHECKOUT
193
+ UNLINK
194
+ UNLOCK
195
+ UPDATE
196
+ UPDATEREDIRECTREF
197
+ VERSION-CONTROL
198
+ ].freeze
199
+
165
200
  # ETag is based on the apache standard of hex mtime-size-inode (inode is 0 on win32)
166
- LINE_END = "\r\n".freeze
167
- REMOTE_ADDR = "REMOTE_ADDR".freeze
168
- HTTP_X_FORWARDED_FOR = "HTTP_X_FORWARDED_FOR".freeze
169
- HTTP_X_FORWARDED_SSL = "HTTP_X_FORWARDED_SSL".freeze
170
- HTTP_X_FORWARDED_SCHEME = "HTTP_X_FORWARDED_SCHEME".freeze
171
- HTTP_X_FORWARDED_PROTO = "HTTP_X_FORWARDED_PROTO".freeze
201
+ LINE_END = "\r\n"
202
+ REMOTE_ADDR = "REMOTE_ADDR"
203
+ HTTP_X_FORWARDED_FOR = "HTTP_X_FORWARDED_FOR"
204
+ HTTP_X_FORWARDED_SSL = "HTTP_X_FORWARDED_SSL"
205
+ HTTP_X_FORWARDED_SCHEME = "HTTP_X_FORWARDED_SCHEME"
206
+ HTTP_X_FORWARDED_PROTO = "HTTP_X_FORWARDED_PROTO"
207
+
208
+ SERVER_NAME = "SERVER_NAME"
209
+ SERVER_PORT = "SERVER_PORT"
210
+ HTTP_HOST = "HTTP_HOST"
211
+ PORT_80 = "80"
212
+ PORT_443 = "443"
213
+ LOCALHOST = "localhost"
214
+ LOCALHOST_IPV4 = "127.0.0.1"
215
+ LOCALHOST_IPV6 = "::1"
216
+ UNSPECIFIED_IPV4 = "0.0.0.0"
217
+ UNSPECIFIED_IPV6 = "::"
172
218
 
173
- SERVER_NAME = "SERVER_NAME".freeze
174
- SERVER_PORT = "SERVER_PORT".freeze
175
- HTTP_HOST = "HTTP_HOST".freeze
176
- PORT_80 = "80".freeze
177
- PORT_443 = "443".freeze
178
- LOCALHOST = "localhost".freeze
179
- LOCALHOST_IP = "127.0.0.1".freeze
180
- LOCALHOST_ADDR = "127.0.0.1:0".freeze
219
+ SERVER_PROTOCOL = "SERVER_PROTOCOL"
220
+ HTTP_11 = "HTTP/1.1"
181
221
 
182
- SERVER_PROTOCOL = "SERVER_PROTOCOL".freeze
183
- HTTP_11 = "HTTP/1.1".freeze
222
+ SERVER_SOFTWARE = "SERVER_SOFTWARE"
223
+ GATEWAY_INTERFACE = "GATEWAY_INTERFACE"
224
+ CGI_VER = "CGI/1.2"
184
225
 
185
- SERVER_SOFTWARE = "SERVER_SOFTWARE".freeze
186
- GATEWAY_INTERFACE = "GATEWAY_INTERFACE".freeze
187
- CGI_VER = "CGI/1.2".freeze
226
+ STOP_COMMAND = "?"
227
+ HALT_COMMAND = "!"
228
+ RESTART_COMMAND = "R"
188
229
 
189
- STOP_COMMAND = "?".freeze
190
- HALT_COMMAND = "!".freeze
191
- RESTART_COMMAND = "R".freeze
230
+ RACK_INPUT = "rack.input"
231
+ RACK_URL_SCHEME = "rack.url_scheme"
232
+ RACK_AFTER_REPLY = "rack.after_reply"
233
+ PUMA_SOCKET = "puma.socket"
234
+ PUMA_CONFIG = "puma.config"
235
+ PUMA_PEERCERT = "puma.peercert"
192
236
 
193
- RACK_INPUT = "rack.input".freeze
194
- RACK_URL_SCHEME = "rack.url_scheme".freeze
195
- RACK_AFTER_REPLY = "rack.after_reply".freeze
196
- PUMA_SOCKET = "puma.socket".freeze
197
- PUMA_CONFIG = "puma.config".freeze
198
- PUMA_PEERCERT = "puma.peercert".freeze
237
+ HTTP = "http"
238
+ HTTPS = "https"
199
239
 
200
- HTTP = "http".freeze
201
- HTTPS = "https".freeze
240
+ HTTPS_KEY = "HTTPS"
202
241
 
203
- HTTPS_KEY = "HTTPS".freeze
242
+ HTTP_VERSION = "HTTP_VERSION"
243
+ HTTP_CONNECTION = "HTTP_CONNECTION"
244
+ HTTP_EXPECT = "HTTP_EXPECT"
245
+ CONTINUE = "100-continue"
204
246
 
205
- HTTP_VERSION = "HTTP_VERSION".freeze
206
- HTTP_CONNECTION = "HTTP_CONNECTION".freeze
207
- HTTP_EXPECT = "HTTP_EXPECT".freeze
208
- CONTINUE = "100-continue".freeze
247
+ HTTP_11_100 = "HTTP/1.1 100 Continue\r\n\r\n"
248
+ HTTP_11_200 = "HTTP/1.1 200 OK\r\n"
249
+ HTTP_10_200 = "HTTP/1.0 200 OK\r\n"
209
250
 
210
- HTTP_11_100 = "HTTP/1.1 100 Continue\r\n\r\n".freeze
211
- HTTP_11_200 = "HTTP/1.1 200 OK\r\n".freeze
212
- HTTP_10_200 = "HTTP/1.0 200 OK\r\n".freeze
251
+ CLOSE = "close"
252
+ KEEP_ALIVE = "keep-alive"
213
253
 
214
- CLOSE = "close".freeze
215
- KEEP_ALIVE = "keep-alive".freeze
254
+ CONTENT_LENGTH2 = "content-length"
255
+ CONTENT_LENGTH_S = "Content-Length: "
256
+ TRANSFER_ENCODING = "transfer-encoding"
257
+ TRANSFER_ENCODING2 = "HTTP_TRANSFER_ENCODING"
216
258
 
217
- CONTENT_LENGTH2 = "content-length".freeze
218
- CONTENT_LENGTH_S = "Content-Length: ".freeze
219
- TRANSFER_ENCODING = "transfer-encoding".freeze
220
- TRANSFER_ENCODING2 = "HTTP_TRANSFER_ENCODING".freeze
259
+ CONNECTION_CLOSE = "Connection: close\r\n"
260
+ CONNECTION_KEEP_ALIVE = "Connection: Keep-Alive\r\n"
221
261
 
222
- CONNECTION_CLOSE = "Connection: close\r\n".freeze
223
- CONNECTION_KEEP_ALIVE = "Connection: Keep-Alive\r\n".freeze
262
+ TRANSFER_ENCODING_CHUNKED = "Transfer-Encoding: chunked\r\n"
263
+ CLOSE_CHUNKED = "0\r\n\r\n"
224
264
 
225
- TRANSFER_ENCODING_CHUNKED = "Transfer-Encoding: chunked\r\n".freeze
226
- CLOSE_CHUNKED = "0\r\n\r\n".freeze
265
+ CHUNKED = "chunked"
227
266
 
228
- CHUNKED = "chunked".freeze
267
+ COLON = ": "
229
268
 
230
- COLON = ": ".freeze
269
+ NEWLINE = "\n"
231
270
 
232
- NEWLINE = "\n".freeze
233
- HTTP_INJECTION_REGEX = /[\r\n]/.freeze
271
+ HIJACK_P = "rack.hijack?"
272
+ HIJACK = "rack.hijack"
273
+ HIJACK_IO = "rack.hijack_io"
234
274
 
235
- HIJACK_P = "rack.hijack?".freeze
236
- HIJACK = "rack.hijack".freeze
237
- HIJACK_IO = "rack.hijack_io".freeze
275
+ EARLY_HINTS = "rack.early_hints"
238
276
 
239
- EARLY_HINTS = "rack.early_hints".freeze
277
+ # Illegal character in the key or value of response header
278
+ DQUOTE = "\""
279
+ HTTP_HEADER_DELIMITER = Regexp.escape("(),/:;<=>?@[]{}\\").freeze
280
+ ILLEGAL_HEADER_KEY_REGEX = /[\x00-\x20#{DQUOTE}#{HTTP_HEADER_DELIMITER}]/.freeze
281
+ # header values can contain HTAB?
282
+ ILLEGAL_HEADER_VALUE_REGEX = /[\x00-\x08\x0A-\x1F]/.freeze
240
283
 
241
- # Mininum interval to checks worker health
242
- WORKER_CHECK_INTERVAL = 5
284
+ # Banned keys of response header
285
+ BANNED_HEADER_KEY = /\A(rack\.|status\z)/.freeze
243
286
 
287
+ PROXY_PROTOCOL_V1_REGEX = /^PROXY (?:TCP4|TCP6|UNKNOWN) ([^\r]+)\r\n/.freeze
244
288
  end
245
289
  end
@@ -11,7 +11,33 @@ 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
+ # commands that cannot be used in a request
37
+ NO_REQ_COMMANDS = %w[info reopen-log worker-count-down worker-count-up].freeze
38
+
39
+ # @version 5.0.0
40
+ PRINTABLE_COMMANDS = %w[gc-stats stats thread-backtraces].freeze
15
41
 
16
42
  def initialize(argv, stdout=STDOUT, stderr=STDERR)
17
43
  @state = nil
@@ -22,7 +48,7 @@ module Puma
22
48
  @control_auth_token = nil
23
49
  @config_file = nil
24
50
  @command = nil
25
- @environment = ENV['RACK_ENV']
51
+ @environment = ENV['APP_ENV'] || ENV['RACK_ENV'] || ENV['RAILS_ENV']
26
52
 
27
53
  @argv = argv.dup
28
54
  @stdout = stdout
@@ -30,7 +56,7 @@ module Puma
30
56
  @cli_options = {}
31
57
 
32
58
  opts = OptionParser.new do |o|
33
- o.banner = "Usage: pumactl (-p PID | -P pidfile | -S status_file | -C url -T token | -F config.rb) (#{COMMANDS.join("|")})"
59
+ o.banner = "Usage: pumactl (-p PID | -P pidfile | -S status_file | -C url -T token | -F config.rb) (#{CMD_PATH_SIG_MAP.keys.join("|")})"
34
60
 
35
61
  o.on "-S", "--state PATH", "Where the state file to use is" do |arg|
36
62
  @state = arg
@@ -71,7 +97,7 @@ module Puma
71
97
  end
72
98
 
73
99
  o.on_tail("-V", "--version", "Show version") do
74
- puts Const::PUMA_VERSION
100
+ @stdout.puts Const::PUMA_VERSION
75
101
  exit
76
102
  end
77
103
  end
@@ -81,6 +107,15 @@ module Puma
81
107
 
82
108
  @command = argv.shift
83
109
 
110
+ # check presence of command
111
+ unless @command
112
+ raise "Available commands: #{CMD_PATH_SIG_MAP.keys.join(", ")}"
113
+ end
114
+
115
+ unless CMD_PATH_SIG_MAP.key? @command
116
+ raise "Invalid command: #{@command}"
117
+ end
118
+
84
119
  unless @config_file == '-'
85
120
  environment = @environment || 'development'
86
121
 
@@ -99,16 +134,6 @@ module Puma
99
134
  @pidfile ||= config.options[:pidfile]
100
135
  end
101
136
  end
102
-
103
- # check present of command
104
- unless @command
105
- raise "Available commands: #{COMMANDS.join(", ")}"
106
- end
107
-
108
- unless COMMANDS.include? @command
109
- raise "Invalid command: #{@command}"
110
- end
111
-
112
137
  rescue => e
113
138
  @stdout.puts e.message
114
139
  exit 1
@@ -132,7 +157,7 @@ module Puma
132
157
  @pid = sf.pid
133
158
  elsif @pidfile
134
159
  # get pid from pid_file
135
- File.open(@pidfile) { |f| @pid = f.read.to_i }
160
+ @pid = File.read(@pidfile, mode: 'rb:UTF-8').to_i
136
161
  end
137
162
  end
138
163
 
@@ -140,23 +165,27 @@ module Puma
140
165
  uri = URI.parse @control_url
141
166
 
142
167
  # create server object by scheme
143
- server = case uri.scheme
144
- when "ssl"
145
- require 'openssl'
146
- OpenSSL::SSL::SSLSocket.new(
147
- TCPSocket.new(uri.host, uri.port),
148
- OpenSSL::SSL::SSLContext.new
149
- ).tap(&:connect)
150
- when "tcp"
151
- TCPSocket.new uri.host, uri.port
152
- when "unix"
153
- UNIXSocket.new "#{uri.host}#{uri.path}"
154
- else
155
- raise "Invalid scheme: #{uri.scheme}"
156
- end
157
-
158
- if @command == "status"
159
- message "Puma is started"
168
+ server =
169
+ case uri.scheme
170
+ when 'ssl'
171
+ require 'openssl'
172
+ OpenSSL::SSL::SSLSocket.new(
173
+ TCPSocket.new(uri.host, uri.port),
174
+ OpenSSL::SSL::SSLContext.new)
175
+ .tap { |ssl| ssl.sync_close = true } # default is false
176
+ .tap(&:connect)
177
+ when 'tcp'
178
+ TCPSocket.new uri.host, uri.port
179
+ when 'unix'
180
+ # check for abstract UNIXSocket
181
+ UNIXSocket.new(@control_url.start_with?('unix://@') ?
182
+ "\0#{uri.host}#{uri.path}" : "#{uri.host}#{uri.path}")
183
+ else
184
+ raise "Invalid scheme: #{uri.scheme}"
185
+ end
186
+
187
+ if @command == 'status'
188
+ message 'Puma is started'
160
189
  else
161
190
  url = "/#{@command}"
162
191
 
@@ -164,10 +193,10 @@ module Puma
164
193
  url = url + "?token=#{@control_auth_token}"
165
194
  end
166
195
 
167
- server << "GET #{url} HTTP/1.0\r\n\r\n"
196
+ server.syswrite "GET #{url} HTTP/1.0\r\n\r\n"
168
197
 
169
198
  unless data = server.read
170
- raise "Server closed connection before responding"
199
+ raise 'Server closed connection before responding'
171
200
  end
172
201
 
173
202
  response = data.split("\r\n")
@@ -176,67 +205,59 @@ module Puma
176
205
  raise "Server sent empty response"
177
206
  end
178
207
 
179
- (@http,@code,@message) = response.first.split(" ",3)
208
+ @http, @code, @message = response.first.split(' ',3)
180
209
 
181
- if @code == "403"
182
- raise "Unauthorized access to server (wrong auth token)"
183
- elsif @code == "404"
210
+ if @code == '403'
211
+ raise 'Unauthorized access to server (wrong auth token)'
212
+ elsif @code == '404'
184
213
  raise "Command error: #{response.last}"
185
- elsif @code != "200"
214
+ elsif @code != '200'
186
215
  raise "Bad response from server: #{@code}"
187
216
  end
188
217
 
189
218
  message "Command #{@command} sent success"
190
- message response.last if @command == "stats" || @command == "gc-stats"
219
+ message response.last if PRINTABLE_COMMANDS.include?(@command)
191
220
  end
192
221
  ensure
193
- server.close if server && !server.closed?
222
+ if server
223
+ if uri.scheme == 'ssl'
224
+ server.sysclose
225
+ else
226
+ server.close unless server.closed?
227
+ end
228
+ end
194
229
  end
195
230
 
196
231
  def send_signal
197
232
  unless @pid
198
- raise "Neither pid nor control url available"
233
+ raise 'Neither pid nor control url available'
199
234
  end
200
235
 
201
236
  begin
237
+ sig = CMD_PATH_SIG_MAP[@command]
202
238
 
203
- case @command
204
- when "restart"
205
- Process.kill "SIGUSR2", @pid
206
-
207
- when "halt"
208
- Process.kill "QUIT", @pid
209
-
210
- when "stop"
211
- Process.kill "SIGTERM", @pid
212
-
213
- when "stats"
214
- puts "Stats not available via pid only"
215
- return
216
-
217
- when "reload-worker-directory"
218
- puts "reload-worker-directory not available via pid only"
239
+ if sig.nil?
240
+ @stdout.puts "'#{@command}' not available via pid only"
241
+ @stdout.flush unless @stdout.sync
219
242
  return
220
-
221
- when "phased-restart"
222
- Process.kill "SIGUSR1", @pid
223
-
224
- when "status"
243
+ elsif sig.start_with? 'SIG'
244
+ if Signal.list.key? sig.sub(/\ASIG/, '')
245
+ Process.kill sig, @pid
246
+ else
247
+ raise "Signal '#{sig}' not available'"
248
+ end
249
+ elsif @command == 'status'
225
250
  begin
226
251
  Process.kill 0, @pid
227
- puts "Puma is started"
252
+ @stdout.puts 'Puma is started'
253
+ @stdout.flush unless @stdout.sync
228
254
  rescue Errno::ESRCH
229
- raise "Puma is not running"
255
+ raise 'Puma is not running'
230
256
  end
231
-
232
- return
233
-
234
- else
235
257
  return
236
258
  end
237
-
238
259
  rescue SystemCallError
239
- if @command == "restart"
260
+ if @command == 'restart'
240
261
  start
241
262
  else
242
263
  raise "No pid '#{@pid}' found"
@@ -247,14 +268,13 @@ module Puma
247
268
  end
248
269
 
249
270
  def run
250
- return start if @command == "start"
251
-
271
+ return start if @command == 'start'
252
272
  prepare_configuration
253
273
 
254
- if Puma.windows?
274
+ if Puma.windows? || @control_url && !NO_REQ_COMMANDS.include?(@command)
255
275
  send_request
256
276
  else
257
- @control_url ? send_request : send_signal
277
+ send_signal
258
278
  end
259
279
 
260
280
  rescue => e
@@ -262,9 +282,9 @@ module Puma
262
282
  exit 1
263
283
  end
264
284
 
265
- private
285
+ private
266
286
  def start
267
- require 'puma/cli'
287
+ require_relative 'cli'
268
288
 
269
289
  run_args = []
270
290
 
@@ -276,13 +296,13 @@ module Puma
276
296
  run_args += ["-C", @config_file] if @config_file
277
297
  run_args += ["-e", @environment] if @environment
278
298
 
279
- events = Puma::Events.new @stdout, @stderr
299
+ log_writer = Puma::LogWriter.new(@stdout, @stderr)
280
300
 
281
301
  # replace $0 because puma use it to generate restart command
282
302
  puma_cmd = $0.gsub(/pumactl$/, 'puma')
283
303
  $0 = puma_cmd if File.exist?(puma_cmd)
284
304
 
285
- cli = Puma::CLI.new run_args, events
305
+ cli = Puma::CLI.new run_args, log_writer
286
306
  cli.run
287
307
  end
288
308
  end
data/lib/puma/detect.rb CHANGED
@@ -1,15 +1,46 @@
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
+ HAS_NATIVE_IO_WAIT = ::IO.public_instance_methods(false).include? :wait_readable
12
+
13
+ IS_JRUBY = Object.const_defined? :JRUBY_VERSION
14
+
15
+ IS_OSX = RUBY_PLATFORM.include? 'darwin'
16
+
17
+ IS_WINDOWS = !!(RUBY_PLATFORM =~ /mswin|ming|cygwin/) ||
18
+ IS_JRUBY && RUBY_DESCRIPTION.include?('mswin')
19
+
20
+ IS_LINUX = !(IS_OSX || IS_WINDOWS)
21
+
22
+ # @version 5.2.0
23
+ IS_MRI = (RUBY_ENGINE == 'ruby' || RUBY_ENGINE.nil?)
5
24
 
6
25
  def self.jruby?
7
26
  IS_JRUBY
8
27
  end
9
28
 
10
- IS_WINDOWS = RUBY_PLATFORM =~ /mswin|ming|cygwin/
29
+ def self.osx?
30
+ IS_OSX
31
+ end
11
32
 
12
33
  def self.windows?
13
34
  IS_WINDOWS
14
35
  end
36
+
37
+ # @version 5.0.0
38
+ def self.mri?
39
+ IS_MRI
40
+ end
41
+
42
+ # @version 5.0.0
43
+ def self.forkable?
44
+ HAS_FORK
45
+ end
15
46
  end