puma 3.12.6 → 6.3.0

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

Potentially problematic release.


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

Files changed (100) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +1806 -451
  3. data/LICENSE +23 -20
  4. data/README.md +217 -65
  5. data/bin/puma-wild +3 -9
  6. data/docs/architecture.md +59 -21
  7. data/docs/compile_options.md +55 -0
  8. data/docs/deployment.md +69 -58
  9. data/docs/fork_worker.md +31 -0
  10. data/docs/images/puma-connection-flow-no-reactor.png +0 -0
  11. data/docs/images/puma-connection-flow.png +0 -0
  12. data/docs/images/puma-general-arch.png +0 -0
  13. data/docs/jungle/README.md +9 -0
  14. data/{tools → docs}/jungle/rc.d/README.md +1 -1
  15. data/{tools → docs}/jungle/rc.d/puma +2 -2
  16. data/docs/kubernetes.md +66 -0
  17. data/docs/nginx.md +2 -2
  18. data/docs/plugins.md +22 -12
  19. data/docs/rails_dev_mode.md +28 -0
  20. data/docs/restart.md +47 -22
  21. data/docs/signals.md +13 -11
  22. data/docs/stats.md +142 -0
  23. data/docs/systemd.md +94 -120
  24. data/docs/testing_benchmarks_local_files.md +150 -0
  25. data/docs/testing_test_rackup_ci_files.md +36 -0
  26. data/ext/puma_http11/PumaHttp11Service.java +2 -2
  27. data/ext/puma_http11/ext_help.h +1 -1
  28. data/ext/puma_http11/extconf.rb +61 -3
  29. data/ext/puma_http11/http11_parser.c +103 -117
  30. data/ext/puma_http11/http11_parser.h +2 -2
  31. data/ext/puma_http11/http11_parser.java.rl +22 -38
  32. data/ext/puma_http11/http11_parser.rl +3 -3
  33. data/ext/puma_http11/http11_parser_common.rl +6 -6
  34. data/ext/puma_http11/mini_ssl.c +389 -99
  35. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  36. data/ext/puma_http11/org/jruby/puma/Http11.java +108 -116
  37. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +84 -99
  38. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +248 -92
  39. data/ext/puma_http11/puma_http11.c +49 -57
  40. data/lib/puma/app/status.rb +71 -49
  41. data/lib/puma/binder.rb +244 -150
  42. data/lib/puma/cli.rb +38 -34
  43. data/lib/puma/client.rb +388 -244
  44. data/lib/puma/cluster/worker.rb +180 -0
  45. data/lib/puma/cluster/worker_handle.rb +97 -0
  46. data/lib/puma/cluster.rb +261 -243
  47. data/lib/puma/commonlogger.rb +21 -14
  48. data/lib/puma/configuration.rb +116 -88
  49. data/lib/puma/const.rb +154 -104
  50. data/lib/puma/control_cli.rb +115 -70
  51. data/lib/puma/detect.rb +33 -2
  52. data/lib/puma/dsl.rb +764 -134
  53. data/lib/puma/error_logger.rb +113 -0
  54. data/lib/puma/events.rb +16 -112
  55. data/lib/puma/io_buffer.rb +42 -5
  56. data/lib/puma/jruby_restart.rb +2 -59
  57. data/lib/puma/json_serialization.rb +96 -0
  58. data/lib/puma/launcher/bundle_pruner.rb +104 -0
  59. data/lib/puma/launcher.rb +184 -133
  60. data/lib/puma/log_writer.rb +147 -0
  61. data/lib/puma/minissl/context_builder.rb +93 -0
  62. data/lib/puma/minissl.rb +263 -70
  63. data/lib/puma/null_io.rb +18 -1
  64. data/lib/puma/plugin/systemd.rb +90 -0
  65. data/lib/puma/plugin/tmp_restart.rb +3 -1
  66. data/lib/puma/plugin.rb +7 -13
  67. data/lib/puma/rack/builder.rb +9 -11
  68. data/lib/puma/rack/urlmap.rb +2 -0
  69. data/lib/puma/rack_default.rb +21 -4
  70. data/lib/puma/reactor.rb +93 -315
  71. data/lib/puma/request.rb +671 -0
  72. data/lib/puma/runner.rb +94 -69
  73. data/lib/puma/sd_notify.rb +149 -0
  74. data/lib/puma/server.rb +327 -772
  75. data/lib/puma/single.rb +20 -74
  76. data/lib/puma/state_file.rb +45 -8
  77. data/lib/puma/thread_pool.rb +146 -92
  78. data/lib/puma/util.rb +22 -10
  79. data/lib/puma.rb +60 -5
  80. data/lib/rack/handler/puma.rb +116 -90
  81. data/tools/Dockerfile +16 -0
  82. data/tools/trickletest.rb +0 -1
  83. metadata +54 -32
  84. data/ext/puma_http11/io_buffer.c +0 -155
  85. data/lib/puma/accept_nonblock.rb +0 -23
  86. data/lib/puma/compat.rb +0 -14
  87. data/lib/puma/convenient.rb +0 -25
  88. data/lib/puma/daemon_ext.rb +0 -33
  89. data/lib/puma/delegation.rb +0 -13
  90. data/lib/puma/java_io_buffer.rb +0 -47
  91. data/lib/puma/rack/backports/uri/common_193.rb +0 -33
  92. data/lib/puma/tcp_logger.rb +0 -41
  93. data/tools/jungle/README.md +0 -19
  94. data/tools/jungle/init.d/README.md +0 -61
  95. data/tools/jungle/init.d/puma +0 -421
  96. data/tools/jungle/init.d/run-puma +0 -18
  97. data/tools/jungle/upstart/README.md +0 -61
  98. data/tools/jungle/upstart/puma-manager.conf +0 -31
  99. data/tools/jungle/upstart/puma.conf +0 -69
  100. /data/{tools → docs}/jungle/rc.d/puma.conf +0 -0
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,9 +74,9 @@ 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
 
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,56 +100,41 @@ module Puma
100
100
  # too taxing on performance.
101
101
  module Const
102
102
 
103
- PUMA_VERSION = VERSION = "3.12.6".freeze
104
- CODE_NAME = "Llamas in Pajamas".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.0"
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
-
133
- PATH_INFO = 'PATH_INFO'.freeze
134
-
135
- PUMA_TMP_BASE = "puma".freeze
136
-
137
- # Indicate that we couldn't parse the request
138
- ERROR_400_RESPONSE = "HTTP/1.1 400 Bad Request\r\n\r\n".freeze
139
-
140
- # The standard empty 404 response for bad requests. Use Error4040Handler for custom stuff.
141
- ERROR_404_RESPONSE = "HTTP/1.1 404 Not Found\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\nNOT FOUND".freeze
142
-
143
- # The standard empty 408 response for requests that timed out.
144
- ERROR_408_RESPONSE = "HTTP/1.1 408 Request Timeout\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\n".freeze
145
-
146
- CONTENT_LENGTH = "CONTENT_LENGTH".freeze
147
-
148
- # Indicate that there was an internal error, obviously.
149
- ERROR_500_RESPONSE = "HTTP/1.1 500 Internal Server Error\r\n\r\n".freeze
150
-
151
- # A common header for indicating the server is too busy. Not used yet.
152
- ERROR_503_RESPONSE = "HTTP/1.1 503 Service Unavailable\r\n\r\nBUSY".freeze
115
+ REQUEST_URI= "REQUEST_URI"
116
+ REQUEST_PATH = "REQUEST_PATH"
117
+ QUERY_STRING = "QUERY_STRING"
118
+ CONTENT_LENGTH = "CONTENT_LENGTH"
119
+
120
+ PATH_INFO = "PATH_INFO"
121
+
122
+ PUMA_TMP_BASE = "puma"
123
+
124
+ ERROR_RESPONSE = {
125
+ # Indicate that we couldn't parse the request
126
+ 400 => "HTTP/1.1 400 Bad Request\r\n\r\n",
127
+ # The standard empty 404 response for bad requests. Use Error4040Handler for custom stuff.
128
+ 404 => "HTTP/1.1 404 Not Found\r\nConnection: close\r\n\r\n",
129
+ # The standard empty 408 response for requests that timed out.
130
+ 408 => "HTTP/1.1 408 Request Timeout\r\nConnection: close\r\n\r\n",
131
+ # Indicate that there was an internal error, obviously.
132
+ 500 => "HTTP/1.1 500 Internal Server Error\r\n\r\n",
133
+ # Incorrect or invalid header value
134
+ 501 => "HTTP/1.1 501 Not Implemented\r\n\r\n",
135
+ # A common header for indicating the server is too busy. Not used yet.
136
+ 503 => "HTTP/1.1 503 Service Unavailable\r\n\r\n"
137
+ }.freeze
153
138
 
154
139
  # The basic max request size we'll try to read.
155
140
  CHUNK_SIZE = 16 * 1024
@@ -161,79 +146,144 @@ module Puma
161
146
  # Maximum request body size before it is moved out of memory and into a tempfile for reading.
162
147
  MAX_BODY = MAX_HEADER
163
148
 
164
- REQUEST_METHOD = "REQUEST_METHOD".freeze
165
- 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
+
166
200
  # ETag is based on the apache standard of hex mtime-size-inode (inode is 0 on win32)
167
- LINE_END = "\r\n".freeze
168
- REMOTE_ADDR = "REMOTE_ADDR".freeze
169
- HTTP_X_FORWARDED_FOR = "HTTP_X_FORWARDED_FOR".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 = "::"
218
+
219
+ SERVER_PROTOCOL = "SERVER_PROTOCOL"
220
+ HTTP_11 = "HTTP/1.1"
221
+
222
+ SERVER_SOFTWARE = "SERVER_SOFTWARE"
223
+ GATEWAY_INTERFACE = "GATEWAY_INTERFACE"
224
+ CGI_VER = "CGI/1.2"
170
225
 
171
- SERVER_NAME = "SERVER_NAME".freeze
172
- SERVER_PORT = "SERVER_PORT".freeze
173
- HTTP_HOST = "HTTP_HOST".freeze
174
- PORT_80 = "80".freeze
175
- PORT_443 = "443".freeze
176
- LOCALHOST = "localhost".freeze
177
- LOCALHOST_IP = "127.0.0.1".freeze
178
- LOCALHOST_ADDR = "127.0.0.1:0".freeze
226
+ STOP_COMMAND = "?"
227
+ HALT_COMMAND = "!"
228
+ RESTART_COMMAND = "R"
179
229
 
180
- SERVER_PROTOCOL = "SERVER_PROTOCOL".freeze
181
- HTTP_11 = "HTTP/1.1".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"
182
236
 
183
- SERVER_SOFTWARE = "SERVER_SOFTWARE".freeze
184
- GATEWAY_INTERFACE = "GATEWAY_INTERFACE".freeze
185
- CGI_VER = "CGI/1.2".freeze
237
+ HTTP = "http"
238
+ HTTPS = "https"
186
239
 
187
- STOP_COMMAND = "?".freeze
188
- HALT_COMMAND = "!".freeze
189
- RESTART_COMMAND = "R".freeze
240
+ HTTPS_KEY = "HTTPS"
190
241
 
191
- RACK_INPUT = "rack.input".freeze
192
- RACK_URL_SCHEME = "rack.url_scheme".freeze
193
- RACK_AFTER_REPLY = "rack.after_reply".freeze
194
- PUMA_SOCKET = "puma.socket".freeze
195
- PUMA_CONFIG = "puma.config".freeze
196
- PUMA_PEERCERT = "puma.peercert".freeze
242
+ HTTP_VERSION = "HTTP_VERSION"
243
+ HTTP_CONNECTION = "HTTP_CONNECTION"
244
+ HTTP_EXPECT = "HTTP_EXPECT"
245
+ CONTINUE = "100-continue"
197
246
 
198
- HTTP = "http".freeze
199
- HTTPS = "https".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"
200
250
 
201
- HTTPS_KEY = "HTTPS".freeze
251
+ CLOSE = "close"
252
+ KEEP_ALIVE = "keep-alive"
202
253
 
203
- HTTP_VERSION = "HTTP_VERSION".freeze
204
- HTTP_CONNECTION = "HTTP_CONNECTION".freeze
205
- HTTP_EXPECT = "HTTP_EXPECT".freeze
206
- CONTINUE = "100-continue".freeze
254
+ CONTENT_LENGTH2 = "content-length"
255
+ CONTENT_LENGTH_S = "Content-Length: "
256
+ TRANSFER_ENCODING = "transfer-encoding"
257
+ TRANSFER_ENCODING2 = "HTTP_TRANSFER_ENCODING"
207
258
 
208
- HTTP_11_100 = "HTTP/1.1 100 Continue\r\n\r\n".freeze
209
- HTTP_11_200 = "HTTP/1.1 200 OK\r\n".freeze
210
- HTTP_10_200 = "HTTP/1.0 200 OK\r\n".freeze
259
+ CONNECTION_CLOSE = "Connection: close\r\n"
260
+ CONNECTION_KEEP_ALIVE = "Connection: Keep-Alive\r\n"
211
261
 
212
- CLOSE = "close".freeze
213
- KEEP_ALIVE = "keep-alive".freeze
262
+ TRANSFER_ENCODING_CHUNKED = "Transfer-Encoding: chunked\r\n"
263
+ CLOSE_CHUNKED = "0\r\n\r\n"
214
264
 
215
- CONTENT_LENGTH2 = "content-length".freeze
216
- CONTENT_LENGTH_S = "Content-Length: ".freeze
217
- TRANSFER_ENCODING = "transfer-encoding".freeze
218
- TRANSFER_ENCODING2 = "HTTP_TRANSFER_ENCODING".freeze
265
+ CHUNKED = "chunked"
219
266
 
220
- CONNECTION_CLOSE = "Connection: close\r\n".freeze
221
- CONNECTION_KEEP_ALIVE = "Connection: Keep-Alive\r\n".freeze
267
+ COLON = ": "
222
268
 
223
- TRANSFER_ENCODING_CHUNKED = "Transfer-Encoding: chunked\r\n".freeze
224
- CLOSE_CHUNKED = "0\r\n\r\n".freeze
269
+ NEWLINE = "\n"
225
270
 
226
- CHUNKED = "chunked".freeze
271
+ HIJACK_P = "rack.hijack?"
272
+ HIJACK = "rack.hijack"
273
+ HIJACK_IO = "rack.hijack_io"
227
274
 
228
- COLON = ": ".freeze
275
+ EARLY_HINTS = "rack.early_hints"
229
276
 
230
- NEWLINE = "\n".freeze
231
- HTTP_INJECTION_REGEX = /[\r\n]/.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
232
283
 
233
- HIJACK_P = "rack.hijack?".freeze
234
- HIJACK = "rack.hijack".freeze
235
- HIJACK_IO = "rack.hijack_io".freeze
284
+ # Banned keys of response header
285
+ BANNED_HEADER_KEY = /\A(rack\.|status\z)/.freeze
236
286
 
237
- EARLY_HINTS = "rack.early_hints".freeze
287
+ PROXY_PROTOCOL_V1_REGEX = /^PROXY (?:TCP4|TCP6|UNKNOWN) ([^\r]+)\r\n/.freeze
238
288
  end
239
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,6 +48,7 @@ module Puma
22
48
  @control_auth_token = nil
23
49
  @config_file = nil
24
50
  @command = nil
51
+ @environment = ENV['APP_ENV'] || ENV['RACK_ENV'] || ENV['RAILS_ENV']
25
52
 
26
53
  @argv = argv.dup
27
54
  @stdout = stdout
@@ -29,7 +56,7 @@ module Puma
29
56
  @cli_options = {}
30
57
 
31
58
  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("|")})"
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("|")})"
33
60
 
34
61
  o.on "-S", "--state PATH", "Where the state file to use is" do |arg|
35
62
  @state = arg
@@ -59,13 +86,18 @@ module Puma
59
86
  @config_file = arg
60
87
  end
61
88
 
89
+ o.on "-e", "--environment ENVIRONMENT",
90
+ "The environment to run the Rack app on (default development)" do |arg|
91
+ @environment = arg
92
+ end
93
+
62
94
  o.on_tail("-H", "--help", "Show this message") do
63
95
  @stdout.puts o
64
96
  exit
65
97
  end
66
98
 
67
99
  o.on_tail("-V", "--version", "Show version") do
68
- puts Const::PUMA_VERSION
100
+ @stdout.puts Const::PUMA_VERSION
69
101
  exit
70
102
  end
71
103
  end
@@ -75,9 +107,22 @@ module Puma
75
107
 
76
108
  @command = argv.shift
77
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
+
78
119
  unless @config_file == '-'
79
- if @config_file.nil? and File.exist?('config/puma.rb')
80
- @config_file = 'config/puma.rb'
120
+ environment = @environment || 'development'
121
+
122
+ if @config_file.nil?
123
+ @config_file = %W(config/puma/#{environment}.rb config/puma.rb).find do |f|
124
+ File.exist?(f)
125
+ end
81
126
  end
82
127
 
83
128
  if @config_file
@@ -89,19 +134,8 @@ module Puma
89
134
  @pidfile ||= config.options[:pidfile]
90
135
  end
91
136
  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
137
  rescue => e
103
138
  @stdout.puts e.message
104
- @stdout.puts e.backtrace
105
139
  exit 1
106
140
  end
107
141
 
@@ -123,7 +157,7 @@ module Puma
123
157
  @pid = sf.pid
124
158
  elsif @pidfile
125
159
  # get pid from pid_file
126
- @pid = File.open(@pidfile).gets.to_i
160
+ @pid = File.read(@pidfile, mode: 'rb:UTF-8').to_i
127
161
  end
128
162
  end
129
163
 
@@ -131,17 +165,27 @@ module Puma
131
165
  uri = URI.parse @control_url
132
166
 
133
167
  # 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"
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'
145
189
  else
146
190
  url = "/#{@command}"
147
191
 
@@ -149,10 +193,10 @@ module Puma
149
193
  url = url + "?token=#{@control_auth_token}"
150
194
  end
151
195
 
152
- server << "GET #{url} HTTP/1.0\r\n\r\n"
196
+ server.syswrite "GET #{url} HTTP/1.0\r\n\r\n"
153
197
 
154
198
  unless data = server.read
155
- raise "Server closed connection before responding"
199
+ raise 'Server closed connection before responding'
156
200
  end
157
201
 
158
202
  response = data.split("\r\n")
@@ -161,57 +205,59 @@ module Puma
161
205
  raise "Server sent empty response"
162
206
  end
163
207
 
164
- (@http,@code,@message) = response.first.split(" ",3)
208
+ @http, @code, @message = response.first.split(' ',3)
165
209
 
166
- if @code == "403"
167
- raise "Unauthorized access to server (wrong auth token)"
168
- elsif @code == "404"
210
+ if @code == '403'
211
+ raise 'Unauthorized access to server (wrong auth token)'
212
+ elsif @code == '404'
169
213
  raise "Command error: #{response.last}"
170
- elsif @code != "200"
214
+ elsif @code != '200'
171
215
  raise "Bad response from server: #{@code}"
172
216
  end
173
217
 
174
218
  message "Command #{@command} sent success"
175
- message response.last if @command == "stats" || @command == "gc-stats"
219
+ message response.last if PRINTABLE_COMMANDS.include?(@command)
176
220
  end
177
221
  ensure
178
- 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
179
229
  end
180
230
 
181
231
  def send_signal
182
232
  unless @pid
183
- raise "Neither pid nor control url available"
233
+ raise 'Neither pid nor control url available'
184
234
  end
185
235
 
186
236
  begin
237
+ sig = CMD_PATH_SIG_MAP[@command]
187
238
 
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"
239
+ if sig.nil?
240
+ @stdout.puts "'#{@command}' not available via pid only"
241
+ @stdout.flush unless @stdout.sync
204
242
  return
205
-
206
- when "phased-restart"
207
- Process.kill "SIGUSR1", @pid
208
-
209
- else
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'
250
+ begin
251
+ Process.kill 0, @pid
252
+ @stdout.puts 'Puma is started'
253
+ @stdout.flush unless @stdout.sync
254
+ rescue Errno::ESRCH
255
+ raise 'Puma is not running'
256
+ end
210
257
  return
211
258
  end
212
-
213
259
  rescue SystemCallError
214
- if @command == "restart"
260
+ if @command == 'restart'
215
261
  start
216
262
  else
217
263
  raise "No pid '#{@pid}' found"
@@ -222,25 +268,23 @@ module Puma
222
268
  end
223
269
 
224
270
  def run
225
- return start if @command == "start"
226
-
271
+ return start if @command == 'start'
227
272
  prepare_configuration
228
273
 
229
- if Puma.windows?
274
+ if Puma.windows? || @control_url && !NO_REQ_COMMANDS.include?(@command)
230
275
  send_request
231
276
  else
232
- @control_url ? send_request : send_signal
277
+ send_signal
233
278
  end
234
279
 
235
280
  rescue => e
236
281
  message e.message
237
- message e.backtrace
238
282
  exit 1
239
283
  end
240
284
 
241
- private
285
+ private
242
286
  def start
243
- require 'puma/cli'
287
+ require_relative 'cli'
244
288
 
245
289
  run_args = []
246
290
 
@@ -250,14 +294,15 @@ module Puma
250
294
  run_args += ["--control-url", @control_url] if @control_url
251
295
  run_args += ["--control-token", @control_auth_token] if @control_auth_token
252
296
  run_args += ["-C", @config_file] if @config_file
297
+ run_args += ["-e", @environment] if @environment
253
298
 
254
- events = Puma::Events.new @stdout, @stderr
299
+ log_writer = Puma::LogWriter.new(@stdout, @stderr)
255
300
 
256
301
  # replace $0 because puma use it to generate restart command
257
302
  puma_cmd = $0.gsub(/pumactl$/, 'puma')
258
303
  $0 = puma_cmd if File.exist?(puma_cmd)
259
304
 
260
- cli = Puma::CLI.new run_args, events
305
+ cli = Puma::CLI.new run_args, log_writer
261
306
  cli.run
262
307
  end
263
308
  end