puma 4.3.0-java

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 (81) hide show
  1. checksums.yaml +7 -0
  2. data/History.md +1532 -0
  3. data/LICENSE +26 -0
  4. data/README.md +291 -0
  5. data/bin/puma +10 -0
  6. data/bin/puma-wild +31 -0
  7. data/bin/pumactl +12 -0
  8. data/docs/architecture.md +37 -0
  9. data/docs/deployment.md +111 -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/nginx.md +80 -0
  14. data/docs/plugins.md +38 -0
  15. data/docs/restart.md +41 -0
  16. data/docs/signals.md +96 -0
  17. data/docs/systemd.md +290 -0
  18. data/docs/tcp_mode.md +96 -0
  19. data/ext/puma_http11/PumaHttp11Service.java +19 -0
  20. data/ext/puma_http11/ext_help.h +15 -0
  21. data/ext/puma_http11/extconf.rb +28 -0
  22. data/ext/puma_http11/http11_parser.c +1044 -0
  23. data/ext/puma_http11/http11_parser.h +65 -0
  24. data/ext/puma_http11/http11_parser.java.rl +145 -0
  25. data/ext/puma_http11/http11_parser.rl +147 -0
  26. data/ext/puma_http11/http11_parser_common.rl +54 -0
  27. data/ext/puma_http11/io_buffer.c +155 -0
  28. data/ext/puma_http11/mini_ssl.c +553 -0
  29. data/ext/puma_http11/org/jruby/puma/Http11.java +226 -0
  30. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +455 -0
  31. data/ext/puma_http11/org/jruby/puma/IOBuffer.java +72 -0
  32. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +363 -0
  33. data/ext/puma_http11/puma_http11.c +502 -0
  34. data/lib/puma.rb +31 -0
  35. data/lib/puma/accept_nonblock.rb +29 -0
  36. data/lib/puma/app/status.rb +80 -0
  37. data/lib/puma/binder.rb +385 -0
  38. data/lib/puma/cli.rb +239 -0
  39. data/lib/puma/client.rb +494 -0
  40. data/lib/puma/cluster.rb +554 -0
  41. data/lib/puma/commonlogger.rb +108 -0
  42. data/lib/puma/configuration.rb +362 -0
  43. data/lib/puma/const.rb +235 -0
  44. data/lib/puma/control_cli.rb +289 -0
  45. data/lib/puma/detect.rb +15 -0
  46. data/lib/puma/dsl.rb +740 -0
  47. data/lib/puma/events.rb +156 -0
  48. data/lib/puma/io_buffer.rb +4 -0
  49. data/lib/puma/jruby_restart.rb +84 -0
  50. data/lib/puma/launcher.rb +475 -0
  51. data/lib/puma/minissl.rb +278 -0
  52. data/lib/puma/minissl/context_builder.rb +76 -0
  53. data/lib/puma/null_io.rb +44 -0
  54. data/lib/puma/plugin.rb +120 -0
  55. data/lib/puma/plugin/tmp_restart.rb +36 -0
  56. data/lib/puma/puma_http11.jar +0 -0
  57. data/lib/puma/rack/builder.rb +301 -0
  58. data/lib/puma/rack/urlmap.rb +93 -0
  59. data/lib/puma/rack_default.rb +9 -0
  60. data/lib/puma/reactor.rb +400 -0
  61. data/lib/puma/runner.rb +192 -0
  62. data/lib/puma/server.rb +1030 -0
  63. data/lib/puma/single.rb +123 -0
  64. data/lib/puma/state_file.rb +31 -0
  65. data/lib/puma/tcp_logger.rb +41 -0
  66. data/lib/puma/thread_pool.rb +328 -0
  67. data/lib/puma/util.rb +124 -0
  68. data/lib/rack/handler/puma.rb +115 -0
  69. data/tools/docker/Dockerfile +16 -0
  70. data/tools/jungle/README.md +19 -0
  71. data/tools/jungle/init.d/README.md +61 -0
  72. data/tools/jungle/init.d/puma +421 -0
  73. data/tools/jungle/init.d/run-puma +18 -0
  74. data/tools/jungle/rc.d/README.md +74 -0
  75. data/tools/jungle/rc.d/puma +61 -0
  76. data/tools/jungle/rc.d/puma.conf +10 -0
  77. data/tools/jungle/upstart/README.md +61 -0
  78. data/tools/jungle/upstart/puma-manager.conf +31 -0
  79. data/tools/jungle/upstart/puma.conf +69 -0
  80. data/tools/trickletest.rb +44 -0
  81. metadata +144 -0
@@ -0,0 +1,235 @@
1
+ #encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ module Puma
5
+ class UnsupportedOption < RuntimeError
6
+ end
7
+
8
+
9
+ # Every standard HTTP code mapped to the appropriate message. These are
10
+ # used so frequently that they are placed directly in Puma for easy
11
+ # access rather than Puma::Const itself.
12
+
13
+ # Every standard HTTP code mapped to the appropriate message.
14
+ # Generated with:
15
+ # curl -s https://www.iana.org/assignments/http-status-codes/http-status-codes-1.csv | \
16
+ # ruby -ne 'm = /^(\d{3}),(?!Unassigned|\(Unused\))([^,]+)/.match($_) and \
17
+ # puts "#{m[1]} => \x27#{m[2].strip}\x27,"'
18
+ HTTP_STATUS_CODES = {
19
+ 100 => 'Continue',
20
+ 101 => 'Switching Protocols',
21
+ 102 => 'Processing',
22
+ 200 => 'OK',
23
+ 201 => 'Created',
24
+ 202 => 'Accepted',
25
+ 203 => 'Non-Authoritative Information',
26
+ 204 => 'No Content',
27
+ 205 => 'Reset Content',
28
+ 206 => 'Partial Content',
29
+ 207 => 'Multi-Status',
30
+ 208 => 'Already Reported',
31
+ 226 => 'IM Used',
32
+ 300 => 'Multiple Choices',
33
+ 301 => 'Moved Permanently',
34
+ 302 => 'Found',
35
+ 303 => 'See Other',
36
+ 304 => 'Not Modified',
37
+ 305 => 'Use Proxy',
38
+ 307 => 'Temporary Redirect',
39
+ 308 => 'Permanent Redirect',
40
+ 400 => 'Bad Request',
41
+ 401 => 'Unauthorized',
42
+ 402 => 'Payment Required',
43
+ 403 => 'Forbidden',
44
+ 404 => 'Not Found',
45
+ 405 => 'Method Not Allowed',
46
+ 406 => 'Not Acceptable',
47
+ 407 => 'Proxy Authentication Required',
48
+ 408 => 'Request Timeout',
49
+ 409 => 'Conflict',
50
+ 410 => 'Gone',
51
+ 411 => 'Length Required',
52
+ 412 => 'Precondition Failed',
53
+ 413 => 'Payload Too Large',
54
+ 414 => 'URI Too Long',
55
+ 415 => 'Unsupported Media Type',
56
+ 416 => 'Range Not Satisfiable',
57
+ 417 => 'Expectation Failed',
58
+ 418 => 'I\'m A Teapot',
59
+ 421 => 'Misdirected Request',
60
+ 422 => 'Unprocessable Entity',
61
+ 423 => 'Locked',
62
+ 424 => 'Failed Dependency',
63
+ 426 => 'Upgrade Required',
64
+ 428 => 'Precondition Required',
65
+ 429 => 'Too Many Requests',
66
+ 431 => 'Request Header Fields Too Large',
67
+ 451 => 'Unavailable For Legal Reasons',
68
+ 500 => 'Internal Server Error',
69
+ 501 => 'Not Implemented',
70
+ 502 => 'Bad Gateway',
71
+ 503 => 'Service Unavailable',
72
+ 504 => 'Gateway Timeout',
73
+ 505 => 'HTTP Version Not Supported',
74
+ 506 => 'Variant Also Negotiates',
75
+ 507 => 'Insufficient Storage',
76
+ 508 => 'Loop Detected',
77
+ 510 => 'Not Extended',
78
+ 511 => 'Network Authentication Required'
79
+ }
80
+
81
+ # For some HTTP status codes the client only expects headers.
82
+ #
83
+
84
+ STATUS_WITH_NO_ENTITY_BODY = {
85
+ 204 => true,
86
+ 205 => true,
87
+ 304 => true
88
+ }
89
+
90
+ # Frequently used constants when constructing requests or responses. Many times
91
+ # the constant just refers to a string with the same contents. Using these constants
92
+ # gave about a 3% to 10% performance improvement over using the strings directly.
93
+ #
94
+ # The constants are frozen because Hash#[]= when called with a String key dups
95
+ # the String UNLESS the String is frozen. This saves us therefore 2 object
96
+ # allocations when creating the env hash later.
97
+ #
98
+ # While Puma does try to emulate the CGI/1.2 protocol, it does not use the REMOTE_IDENT,
99
+ # REMOTE_USER, or REMOTE_HOST parameters since those are either a security problem or
100
+ # too taxing on performance.
101
+ module Const
102
+
103
+ PUMA_VERSION = VERSION = "4.3.0".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
108
+
109
+ # The default number of seconds for another request within a persistent
110
+ # session.
111
+ PERSISTENT_TIMEOUT = 20
112
+
113
+ # The default number of seconds to wait until we get the first data
114
+ # for the request
115
+ FIRST_DATA_TIMEOUT = 30
116
+
117
+ # How long to wait when getting some write blocking on the socket when
118
+ # sending data back
119
+ WRITE_TIMEOUT = 10
120
+
121
+ # The original URI requested by the client.
122
+ REQUEST_URI= 'REQUEST_URI'.freeze
123
+ REQUEST_PATH = 'REQUEST_PATH'.freeze
124
+ QUERY_STRING = 'QUERY_STRING'.freeze
125
+ CONTENT_LENGTH = "CONTENT_LENGTH".freeze
126
+
127
+ PATH_INFO = 'PATH_INFO'.freeze
128
+
129
+ PUMA_TMP_BASE = "puma".freeze
130
+
131
+ ERROR_RESPONSE = {
132
+ # Indicate that we couldn't parse the request
133
+ 400 => "HTTP/1.1 400 Bad Request\r\n\r\n".freeze,
134
+ # The standard empty 404 response for bad requests. Use Error4040Handler for custom stuff.
135
+ 404 => "HTTP/1.1 404 Not Found\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\nNOT FOUND".freeze,
136
+ # The standard empty 408 response for requests that timed out.
137
+ 408 => "HTTP/1.1 408 Request Timeout\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\n".freeze,
138
+ # Indicate that there was an internal error, obviously.
139
+ 500 => "HTTP/1.1 500 Internal Server Error\r\n\r\n".freeze,
140
+ # A common header for indicating the server is too busy. Not used yet.
141
+ 503 => "HTTP/1.1 503 Service Unavailable\r\n\r\nBUSY".freeze
142
+ }
143
+
144
+ # The basic max request size we'll try to read.
145
+ CHUNK_SIZE = 16 * 1024
146
+
147
+ # This is the maximum header that is allowed before a client is booted. The parser detects
148
+ # this, but we'd also like to do this as well.
149
+ MAX_HEADER = 1024 * (80 + 32)
150
+
151
+ # Maximum request body size before it is moved out of memory and into a tempfile for reading.
152
+ MAX_BODY = MAX_HEADER
153
+
154
+ REQUEST_METHOD = "REQUEST_METHOD".freeze
155
+ HEAD = "HEAD".freeze
156
+ # ETag is based on the apache standard of hex mtime-size-inode (inode is 0 on win32)
157
+ LINE_END = "\r\n".freeze
158
+ REMOTE_ADDR = "REMOTE_ADDR".freeze
159
+ HTTP_X_FORWARDED_FOR = "HTTP_X_FORWARDED_FOR".freeze
160
+ HTTP_X_FORWARDED_SSL = "HTTP_X_FORWARDED_SSL".freeze
161
+ HTTP_X_FORWARDED_SCHEME = "HTTP_X_FORWARDED_SCHEME".freeze
162
+ HTTP_X_FORWARDED_PROTO = "HTTP_X_FORWARDED_PROTO".freeze
163
+
164
+ SERVER_NAME = "SERVER_NAME".freeze
165
+ SERVER_PORT = "SERVER_PORT".freeze
166
+ HTTP_HOST = "HTTP_HOST".freeze
167
+ PORT_80 = "80".freeze
168
+ PORT_443 = "443".freeze
169
+ LOCALHOST = "localhost".freeze
170
+ LOCALHOST_IP = "127.0.0.1".freeze
171
+ LOCALHOST_ADDR = "127.0.0.1:0".freeze
172
+
173
+ SERVER_PROTOCOL = "SERVER_PROTOCOL".freeze
174
+ HTTP_11 = "HTTP/1.1".freeze
175
+
176
+ SERVER_SOFTWARE = "SERVER_SOFTWARE".freeze
177
+ GATEWAY_INTERFACE = "GATEWAY_INTERFACE".freeze
178
+ CGI_VER = "CGI/1.2".freeze
179
+
180
+ STOP_COMMAND = "?".freeze
181
+ HALT_COMMAND = "!".freeze
182
+ RESTART_COMMAND = "R".freeze
183
+
184
+ RACK_INPUT = "rack.input".freeze
185
+ RACK_URL_SCHEME = "rack.url_scheme".freeze
186
+ RACK_AFTER_REPLY = "rack.after_reply".freeze
187
+ PUMA_SOCKET = "puma.socket".freeze
188
+ PUMA_CONFIG = "puma.config".freeze
189
+ PUMA_PEERCERT = "puma.peercert".freeze
190
+
191
+ HTTP = "http".freeze
192
+ HTTPS = "https".freeze
193
+
194
+ HTTPS_KEY = "HTTPS".freeze
195
+
196
+ HTTP_VERSION = "HTTP_VERSION".freeze
197
+ HTTP_CONNECTION = "HTTP_CONNECTION".freeze
198
+ HTTP_EXPECT = "HTTP_EXPECT".freeze
199
+ CONTINUE = "100-continue".freeze
200
+
201
+ HTTP_11_100 = "HTTP/1.1 100 Continue\r\n\r\n".freeze
202
+ HTTP_11_200 = "HTTP/1.1 200 OK\r\n".freeze
203
+ HTTP_10_200 = "HTTP/1.0 200 OK\r\n".freeze
204
+
205
+ CLOSE = "close".freeze
206
+ KEEP_ALIVE = "keep-alive".freeze
207
+
208
+ CONTENT_LENGTH2 = "content-length".freeze
209
+ CONTENT_LENGTH_S = "Content-Length: ".freeze
210
+ TRANSFER_ENCODING = "transfer-encoding".freeze
211
+ TRANSFER_ENCODING2 = "HTTP_TRANSFER_ENCODING".freeze
212
+
213
+ CONNECTION_CLOSE = "Connection: close\r\n".freeze
214
+ CONNECTION_KEEP_ALIVE = "Connection: Keep-Alive\r\n".freeze
215
+
216
+ TRANSFER_ENCODING_CHUNKED = "Transfer-Encoding: chunked\r\n".freeze
217
+ CLOSE_CHUNKED = "0\r\n\r\n".freeze
218
+
219
+ CHUNKED = "chunked".freeze
220
+
221
+ COLON = ": ".freeze
222
+
223
+ NEWLINE = "\n".freeze
224
+
225
+ HIJACK_P = "rack.hijack?".freeze
226
+ HIJACK = "rack.hijack".freeze
227
+ HIJACK_IO = "rack.hijack_io".freeze
228
+
229
+ EARLY_HINTS = "rack.early_hints".freeze
230
+
231
+ # Mininum interval to checks worker health
232
+ WORKER_CHECK_INTERVAL = 5
233
+
234
+ end
235
+ end
@@ -0,0 +1,289 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'optparse'
4
+ require_relative 'state_file'
5
+ require_relative 'const'
6
+ require_relative 'detect'
7
+ require_relative 'configuration'
8
+ require 'uri'
9
+ require 'socket'
10
+
11
+ module Puma
12
+ class ControlCLI
13
+
14
+ COMMANDS = %w{halt restart phased-restart start stats status stop reload-worker-directory gc gc-stats}
15
+
16
+ def initialize(argv, stdout=STDOUT, stderr=STDERR)
17
+ @state = nil
18
+ @quiet = false
19
+ @pidfile = nil
20
+ @pid = nil
21
+ @control_url = nil
22
+ @control_auth_token = nil
23
+ @config_file = nil
24
+ @command = nil
25
+ @environment = ENV['RACK_ENV']
26
+
27
+ @argv = argv.dup
28
+ @stdout = stdout
29
+ @stderr = stderr
30
+ @cli_options = {}
31
+
32
+ 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("|")})"
34
+
35
+ o.on "-S", "--state PATH", "Where the state file to use is" do |arg|
36
+ @state = arg
37
+ end
38
+
39
+ o.on "-Q", "--quiet", "Not display messages" do |arg|
40
+ @quiet = true
41
+ end
42
+
43
+ o.on "-P", "--pidfile PATH", "Pid file" do |arg|
44
+ @pidfile = arg
45
+ end
46
+
47
+ o.on "-p", "--pid PID", "Pid" do |arg|
48
+ @pid = arg.to_i
49
+ end
50
+
51
+ o.on "-C", "--control-url URL", "The bind url to use for the control server" do |arg|
52
+ @control_url = arg
53
+ end
54
+
55
+ o.on "-T", "--control-token TOKEN", "The token to use as authentication for the control server" do |arg|
56
+ @control_auth_token = arg
57
+ end
58
+
59
+ o.on "-F", "--config-file PATH", "Puma config script" do |arg|
60
+ @config_file = arg
61
+ end
62
+
63
+ o.on "-e", "--environment ENVIRONMENT",
64
+ "The environment to run the Rack app on (default development)" do |arg|
65
+ @environment = arg
66
+ end
67
+
68
+ o.on_tail("-H", "--help", "Show this message") do
69
+ @stdout.puts o
70
+ exit
71
+ end
72
+
73
+ o.on_tail("-V", "--version", "Show version") do
74
+ puts Const::PUMA_VERSION
75
+ exit
76
+ end
77
+ end
78
+
79
+ opts.order!(argv) { |a| opts.terminate a }
80
+ opts.parse!
81
+
82
+ @command = argv.shift
83
+
84
+ unless @config_file == '-'
85
+ environment = @environment || 'development'
86
+
87
+ if @config_file.nil?
88
+ @config_file = %W(config/puma/#{environment}.rb config/puma.rb).find do |f|
89
+ File.exist?(f)
90
+ end
91
+ end
92
+
93
+ if @config_file
94
+ config = Puma::Configuration.new({ config_files: [@config_file] }, {})
95
+ config.load
96
+ @state ||= config.options[:state]
97
+ @control_url ||= config.options[:control_url]
98
+ @control_auth_token ||= config.options[:control_auth_token]
99
+ @pidfile ||= config.options[:pidfile]
100
+ end
101
+ 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
+ rescue => e
113
+ @stdout.puts e.message
114
+ exit 1
115
+ end
116
+
117
+ def message(msg)
118
+ @stdout.puts msg unless @quiet
119
+ end
120
+
121
+ def prepare_configuration
122
+ if @state
123
+ unless File.exist? @state
124
+ raise "State file not found: #{@state}"
125
+ end
126
+
127
+ sf = Puma::StateFile.new
128
+ sf.load @state
129
+
130
+ @control_url = sf.control_url
131
+ @control_auth_token = sf.control_auth_token
132
+ @pid = sf.pid
133
+ elsif @pidfile
134
+ # get pid from pid_file
135
+ File.open(@pidfile) { |f| @pid = f.read.to_i }
136
+ end
137
+ end
138
+
139
+ def send_request
140
+ uri = URI.parse @control_url
141
+
142
+ # 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"
160
+ else
161
+ url = "/#{@command}"
162
+
163
+ if @control_auth_token
164
+ url = url + "?token=#{@control_auth_token}"
165
+ end
166
+
167
+ server << "GET #{url} HTTP/1.0\r\n\r\n"
168
+
169
+ unless data = server.read
170
+ raise "Server closed connection before responding"
171
+ end
172
+
173
+ response = data.split("\r\n")
174
+
175
+ if response.empty?
176
+ raise "Server sent empty response"
177
+ end
178
+
179
+ (@http,@code,@message) = response.first.split(" ",3)
180
+
181
+ if @code == "403"
182
+ raise "Unauthorized access to server (wrong auth token)"
183
+ elsif @code == "404"
184
+ raise "Command error: #{response.last}"
185
+ elsif @code != "200"
186
+ raise "Bad response from server: #{@code}"
187
+ end
188
+
189
+ message "Command #{@command} sent success"
190
+ message response.last if @command == "stats" || @command == "gc-stats"
191
+ end
192
+ ensure
193
+ server.close if server && !server.closed?
194
+ end
195
+
196
+ def send_signal
197
+ unless @pid
198
+ raise "Neither pid nor control url available"
199
+ end
200
+
201
+ begin
202
+
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"
219
+ return
220
+
221
+ when "phased-restart"
222
+ Process.kill "SIGUSR1", @pid
223
+
224
+ when "status"
225
+ begin
226
+ Process.kill 0, @pid
227
+ puts "Puma is started"
228
+ rescue Errno::ESRCH
229
+ raise "Puma is not running"
230
+ end
231
+
232
+ return
233
+
234
+ else
235
+ return
236
+ end
237
+
238
+ rescue SystemCallError
239
+ if @command == "restart"
240
+ start
241
+ else
242
+ raise "No pid '#{@pid}' found"
243
+ end
244
+ end
245
+
246
+ message "Command #{@command} sent success"
247
+ end
248
+
249
+ def run
250
+ return start if @command == "start"
251
+
252
+ prepare_configuration
253
+
254
+ if Puma.windows?
255
+ send_request
256
+ else
257
+ @control_url ? send_request : send_signal
258
+ end
259
+
260
+ rescue => e
261
+ message e.message
262
+ exit 1
263
+ end
264
+
265
+ private
266
+ def start
267
+ require 'puma/cli'
268
+
269
+ run_args = []
270
+
271
+ run_args += ["-S", @state] if @state
272
+ run_args += ["-q"] if @quiet
273
+ run_args += ["--pidfile", @pidfile] if @pidfile
274
+ run_args += ["--control-url", @control_url] if @control_url
275
+ run_args += ["--control-token", @control_auth_token] if @control_auth_token
276
+ run_args += ["-C", @config_file] if @config_file
277
+ run_args += ["-e", @environment] if @environment
278
+
279
+ events = Puma::Events.new @stdout, @stderr
280
+
281
+ # replace $0 because puma use it to generate restart command
282
+ puma_cmd = $0.gsub(/pumactl$/, 'puma')
283
+ $0 = puma_cmd if File.exist?(puma_cmd)
284
+
285
+ cli = Puma::CLI.new run_args, events
286
+ cli.run
287
+ end
288
+ end
289
+ end