puma 2.7.0 → 3.1.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 (79) hide show
  1. checksums.yaml +5 -13
  2. data/DEPLOYMENT.md +91 -0
  3. data/Gemfile +3 -2
  4. data/History.txt +624 -1
  5. data/Manifest.txt +15 -3
  6. data/README.md +129 -14
  7. data/Rakefile +3 -3
  8. data/bin/puma-wild +31 -0
  9. data/bin/pumactl +1 -1
  10. data/docs/nginx.md +1 -1
  11. data/docs/signals.md +43 -0
  12. data/ext/puma_http11/extconf.rb +7 -2
  13. data/ext/puma_http11/http11_parser.java.rl +5 -5
  14. data/ext/puma_http11/io_buffer.c +1 -1
  15. data/ext/puma_http11/mini_ssl.c +233 -18
  16. data/ext/puma_http11/org/jruby/puma/Http11.java +12 -3
  17. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +39 -39
  18. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +245 -195
  19. data/ext/puma_http11/puma_http11.c +12 -4
  20. data/lib/puma.rb +1 -0
  21. data/lib/puma/app/status.rb +7 -0
  22. data/lib/puma/binder.rb +108 -39
  23. data/lib/puma/capistrano.rb +23 -6
  24. data/lib/puma/cli.rb +141 -446
  25. data/lib/puma/client.rb +48 -1
  26. data/lib/puma/cluster.rb +207 -58
  27. data/lib/puma/commonlogger.rb +107 -0
  28. data/lib/puma/configuration.rb +262 -235
  29. data/lib/puma/const.rb +97 -14
  30. data/lib/puma/control_cli.rb +85 -77
  31. data/lib/puma/convenient.rb +23 -0
  32. data/lib/puma/daemon_ext.rb +11 -4
  33. data/lib/puma/detect.rb +8 -1
  34. data/lib/puma/dsl.rb +456 -0
  35. data/lib/puma/events.rb +35 -18
  36. data/lib/puma/jruby_restart.rb +1 -1
  37. data/lib/puma/launcher.rb +399 -0
  38. data/lib/puma/minissl.rb +49 -20
  39. data/lib/puma/null_io.rb +15 -0
  40. data/lib/puma/plugin.rb +104 -0
  41. data/lib/puma/plugin/tmp_restart.rb +35 -0
  42. data/lib/puma/rack/backports/uri/common_18.rb +56 -0
  43. data/lib/puma/rack/backports/uri/common_192.rb +52 -0
  44. data/lib/puma/rack/backports/uri/common_193.rb +29 -0
  45. data/lib/puma/rack/builder.rb +295 -0
  46. data/lib/puma/rack/urlmap.rb +90 -0
  47. data/lib/puma/reactor.rb +14 -1
  48. data/lib/puma/runner.rb +35 -17
  49. data/lib/puma/server.rb +161 -58
  50. data/lib/puma/single.rb +15 -10
  51. data/lib/puma/state_file.rb +29 -0
  52. data/lib/puma/thread_pool.rb +88 -13
  53. data/lib/puma/util.rb +123 -0
  54. data/lib/rack/handler/puma.rb +35 -29
  55. data/puma.gemspec +2 -4
  56. data/tools/jungle/init.d/README.md +2 -2
  57. data/tools/jungle/init.d/puma +69 -7
  58. data/tools/jungle/upstart/puma.conf +8 -2
  59. metadata +51 -71
  60. data/COPYING +0 -55
  61. data/TODO +0 -5
  62. data/lib/puma/rack_patch.rb +0 -45
  63. data/test/test_app_status.rb +0 -92
  64. data/test/test_cli.rb +0 -173
  65. data/test/test_config.rb +0 -16
  66. data/test/test_http10.rb +0 -27
  67. data/test/test_http11.rb +0 -145
  68. data/test/test_integration.rb +0 -165
  69. data/test/test_iobuffer.rb +0 -38
  70. data/test/test_minissl.rb +0 -25
  71. data/test/test_null_io.rb +0 -31
  72. data/test/test_persistent.rb +0 -238
  73. data/test/test_puma_server.rb +0 -292
  74. data/test/test_rack_handler.rb +0 -10
  75. data/test/test_rack_server.rb +0 -141
  76. data/test/test_tcp_rack.rb +0 -42
  77. data/test/test_thread_pool.rb +0 -156
  78. data/test/test_unix_socket.rb +0 -39
  79. data/test/test_ws.rb +0 -89
@@ -176,14 +176,12 @@ static VALUE find_common_field_value(const char *field, size_t flen)
176
176
  void http_field(puma_parser* hp, const char *field, size_t flen,
177
177
  const char *value, size_t vlen)
178
178
  {
179
- VALUE v = Qnil;
180
179
  VALUE f = Qnil;
180
+ VALUE v;
181
181
 
182
182
  VALIDATE_MAX_LENGTH(flen, FIELD_NAME);
183
183
  VALIDATE_MAX_LENGTH(vlen, FIELD_VALUE);
184
184
 
185
- v = rb_str_new(value, vlen);
186
-
187
185
  f = find_common_field_value(field, flen);
188
186
 
189
187
  if (f == Qnil) {
@@ -201,7 +199,17 @@ void http_field(puma_parser* hp, const char *field, size_t flen,
201
199
  f = rb_str_new(hp->buf, new_size);
202
200
  }
203
201
 
204
- rb_hash_aset(hp->request, f, v);
202
+ /* check for duplicate header */
203
+ v = rb_hash_aref(hp->request, f);
204
+
205
+ if (v == Qnil) {
206
+ v = rb_str_new(value, vlen);
207
+ rb_hash_aset(hp->request, f, v);
208
+ } else {
209
+ /* if duplicate header, normalize to comma-separated values */
210
+ rb_str_cat2(v, ", ");
211
+ rb_str_cat(v, value, vlen);
212
+ }
205
213
  }
206
214
 
207
215
  void request_method(puma_parser* hp, const char *at, size_t length)
data/lib/puma.rb CHANGED
@@ -12,3 +12,4 @@ require 'thread'
12
12
  # Ruby Puma
13
13
  require 'puma/const'
14
14
  require 'puma/server'
15
+ require 'puma/launcher'
@@ -48,6 +48,13 @@ module Puma
48
48
  return rack_response(200, OK_STATUS)
49
49
  end
50
50
 
51
+ when /\/reload-worker-directory$/
52
+ if !@cli.send(:reload_worker_directory)
53
+ return rack_response(404, '{ "error": "reload_worker_directory not available" }')
54
+ else
55
+ return rack_response(200, OK_STATUS)
56
+ end
57
+
51
58
  when /\/stats$/
52
59
  return rack_response(200, @cli.stats)
53
60
  else
data/lib/puma/binder.rb CHANGED
@@ -1,9 +1,12 @@
1
1
  require 'puma/const'
2
+ require 'uri'
2
3
 
3
4
  module Puma
4
5
  class Binder
5
6
  include Puma::Const
6
7
 
8
+ RACK_VERSION = [1,3].freeze
9
+
7
10
  def initialize(events)
8
11
  @events = events
9
12
  @listeners = []
@@ -11,21 +14,21 @@ module Puma
11
14
  @unix_paths = []
12
15
 
13
16
  @proto_env = {
14
- "rack.version".freeze => Rack::VERSION,
17
+ "rack.version".freeze => RACK_VERSION,
15
18
  "rack.errors".freeze => events.stderr,
16
19
  "rack.multithread".freeze => true,
17
20
  "rack.multiprocess".freeze => false,
18
21
  "rack.run_once".freeze => false,
19
22
  "SCRIPT_NAME".freeze => ENV['SCRIPT_NAME'] || "",
20
23
 
21
- # Rack blows up if this is an empty string, and Rack::Lint
22
- # blows up if it's nil. So 'text/plain' seems like the most
23
- # sensible default value.
24
- "CONTENT_TYPE".freeze => "text/plain",
24
+ # I'd like to set a default CONTENT_TYPE here but some things
25
+ # depend on their not being a default set and infering
26
+ # it from the content. And so if i set it here, it won't
27
+ # infer properly.
25
28
 
26
29
  "QUERY_STRING".freeze => "",
27
30
  SERVER_PROTOCOL => HTTP_11,
28
- SERVER_SOFTWARE => PUMA_VERSION,
31
+ SERVER_SOFTWARE => PUMA_SERVER_STRING,
29
32
  GATEWAY_INTERFACE => CGI_VER
30
33
  }
31
34
 
@@ -87,13 +90,18 @@ module Puma
87
90
  logger.log "* Inherited #{str}"
88
91
  io = inherit_tcp_listener uri.host, uri.port, fd
89
92
  else
93
+ params = Util.parse_query uri.query
94
+
95
+ opt = params.key?('low_latency')
96
+ bak = params.fetch('backlog', 1024).to_i
97
+
90
98
  logger.log "* Listening on #{str}"
91
- io = add_tcp_listener uri.host, uri.port
99
+ io = add_tcp_listener uri.host, uri.port, opt, bak
92
100
  end
93
101
 
94
102
  @listeners << [str, io]
95
103
  when "unix"
96
- path = "#{uri.host}#{uri.path}"
104
+ path = "#{uri.host}#{uri.path}".gsub("%20", " ")
97
105
 
98
106
  if fd = @inherited_fds.delete(str)
99
107
  logger.log "* Inherited #{str}"
@@ -102,42 +110,84 @@ module Puma
102
110
  logger.log "* Listening on #{str}"
103
111
 
104
112
  umask = nil
113
+ mode = nil
114
+ backlog = nil
105
115
 
106
116
  if uri.query
107
- params = Rack::Utils.parse_query uri.query
117
+ params = Util.parse_query uri.query
108
118
  if u = params['umask']
109
119
  # Use Integer() to respect the 0 prefix as octal
110
120
  umask = Integer(u)
111
121
  end
122
+
123
+ if u = params['mode']
124
+ mode = Integer('0'+u)
125
+ end
126
+
127
+ if u = params['backlog']
128
+ backlog = Integer(u)
129
+ end
112
130
  end
113
131
 
114
- io = add_unix_listener path, umask
132
+ io = add_unix_listener path, umask, mode, backlog
115
133
  end
116
134
 
117
135
  @listeners << [str, io]
118
136
  when "ssl"
119
- if IS_JRUBY
120
- @events.error "SSL not supported on JRuby"
121
- raise UnsupportedOption
122
- end
137
+ MiniSSL.check
123
138
 
124
- params = Rack::Utils.parse_query uri.query
139
+ params = Util.parse_query uri.query
125
140
  require 'puma/minissl'
126
141
 
127
142
  ctx = MiniSSL::Context.new
128
- unless params['key']
129
- @events.error "Please specify the SSL key via 'key='"
130
- end
131
143
 
132
- ctx.key = params['key']
144
+ if defined?(JRUBY_VERSION)
145
+ unless params['keystore']
146
+ @events.error "Please specify the Java keystore via 'keystore='"
147
+ end
133
148
 
134
- unless params['cert']
135
- @events.error "Please specify the SSL cert via 'cert='"
136
- end
149
+ ctx.keystore = params['keystore']
137
150
 
138
- ctx.cert = params['cert']
151
+ unless params['keystore-pass']
152
+ @events.error "Please specify the Java keystore password via 'keystore-pass='"
153
+ end
154
+
155
+ ctx.keystore_pass = params['keystore-pass']
156
+ else
157
+ unless params['key']
158
+ @events.error "Please specify the SSL key via 'key='"
159
+ end
160
+
161
+ ctx.key = params['key']
162
+
163
+ unless params['cert']
164
+ @events.error "Please specify the SSL cert via 'cert='"
165
+ end
166
+
167
+ ctx.cert = params['cert']
168
+
169
+ if ['peer', 'force_peer'].include?(params['verify_mode'])
170
+ unless params['ca']
171
+ @events.error "Please specify the SSL ca via 'ca='"
172
+ end
173
+ end
139
174
 
140
- ctx.verify_mode = MiniSSL::VERIFY_NONE
175
+ ctx.ca = params['ca'] if params['ca']
176
+
177
+ if params['verify_mode']
178
+ ctx.verify_mode = case params['verify_mode']
179
+ when "peer"
180
+ MiniSSL::VERIFY_PEER
181
+ when "force_peer"
182
+ MiniSSL::VERIFY_PEER | MiniSSL::VERIFY_FAIL_IF_NO_PEER_CERT
183
+ when "none"
184
+ MiniSSL::VERIFY_NONE
185
+ else
186
+ @events.error "Please specify a valid verify_mode="
187
+ MiniSSL::VERIFY_NONE
188
+ end
189
+ end
190
+ end
141
191
 
142
192
  if fd = @inherited_fds.delete(str)
143
193
  logger.log "* Inherited #{str}"
@@ -186,17 +236,21 @@ module Puma
186
236
  # allow to accumulate before returning connection refused.
187
237
  #
188
238
  def add_tcp_listener(host, port, optimize_for_latency=true, backlog=1024)
189
- host = host[1..-2] if host[0..0] == '['
239
+ host = host[1..-2] if host and host[0..0] == '['
190
240
  s = TCPServer.new(host, port)
191
241
  if optimize_for_latency
192
242
  s.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
193
243
  end
194
244
  s.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
195
245
  s.listen backlog
246
+ @connected_port = s.addr[1]
247
+
196
248
  @ios << s
197
249
  s
198
250
  end
199
251
 
252
+ attr_reader :connected_port
253
+
200
254
  def inherit_tcp_listener(host, port, fd)
201
255
  if fd.kind_of? TCPServer
202
256
  s = fd
@@ -210,13 +264,11 @@ module Puma
210
264
 
211
265
  def add_ssl_listener(host, port, ctx,
212
266
  optimize_for_latency=true, backlog=1024)
213
- if IS_JRUBY
214
- @events.error "SSL not supported on JRuby"
215
- raise UnsupportedOption
216
- end
217
-
218
267
  require 'puma/minissl'
219
268
 
269
+ MiniSSL.check
270
+
271
+ host = host[1..-2] if host[0..0] == '['
220
272
  s = TCPServer.new(host, port)
221
273
  if optimize_for_latency
222
274
  s.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
@@ -234,20 +286,24 @@ module Puma
234
286
  end
235
287
 
236
288
  def inherited_ssl_listener(fd, ctx)
237
- if IS_JRUBY
238
- @events.error "SSL not supported on JRuby"
239
- raise UnsupportedOption
240
- end
241
-
242
289
  require 'puma/minissl'
290
+ MiniSSL.check
291
+
243
292
  s = TCPServer.for_fd(fd)
244
- @ios << MiniSSL::Server.new(s, ctx)
293
+ ssl = MiniSSL::Server.new(s, ctx)
294
+
295
+ env = @proto_env.dup
296
+ env[HTTPS_KEY] = HTTPS
297
+ @envs[ssl] = env
298
+
299
+ @ios << ssl
300
+
245
301
  s
246
302
  end
247
303
 
248
304
  # Tell the server to listen on +path+ as a UNIX domain socket.
249
305
  #
250
- def add_unix_listener(path, umask=nil)
306
+ def add_unix_listener(path, umask=nil, mode=nil, backlog=nil)
251
307
  @unix_paths << path
252
308
 
253
309
  # Let anyone connect by default
@@ -256,10 +312,10 @@ module Puma
256
312
  begin
257
313
  old_mask = File.umask(umask)
258
314
 
259
- if File.exists? path
315
+ if File.exist? path
260
316
  begin
261
317
  old = UNIXSocket.new path
262
- rescue SystemCallError
318
+ rescue SystemCallError, IOError
263
319
  File.unlink path
264
320
  else
265
321
  old.close
@@ -268,11 +324,20 @@ module Puma
268
324
  end
269
325
 
270
326
  s = UNIXServer.new(path)
327
+ s.listen backlog if backlog
271
328
  @ios << s
272
329
  ensure
273
330
  File.umask old_mask
274
331
  end
275
332
 
333
+ if mode
334
+ File.chmod mode, path
335
+ end
336
+
337
+ env = @proto_env.dup
338
+ env[REMOTE_ADDR] = "127.0.0.1"
339
+ @envs[s] = env
340
+
276
341
  s
277
342
  end
278
343
 
@@ -286,6 +351,10 @@ module Puma
286
351
  end
287
352
  @ios << s
288
353
 
354
+ env = @proto_env.dup
355
+ env[REMOTE_ADDR] = "127.0.0.1"
356
+ @envs[s] = env
357
+
289
358
  s
290
359
  end
291
360
 
@@ -1,13 +1,13 @@
1
+ $stderr.puts "DEPRECATED: To manage puma with capistrano, use https://github.com/seuros/capistrano-puma"
2
+
1
3
  Capistrano::Configuration.instance.load do
2
- after 'deploy:stop', 'puma:stop'
3
- after 'deploy:start', 'puma:start'
4
- after 'deploy:restart', 'puma:restart'
5
4
 
6
5
  # Ensure the tmp/sockets directory is created by the deploy:setup task and
7
6
  # symlinked in by the deploy:update task. This is not handled by Capistrano
8
7
  # v2 but is fixed in v3.
9
8
  shared_children.push('tmp/sockets')
10
9
 
10
+ _cset(:puma_default_hooks) { true }
11
11
  _cset(:puma_cmd) { "#{fetch(:bundle_cmd, 'bundle')} exec puma" }
12
12
  _cset(:pumactl_cmd) { "#{fetch(:bundle_cmd, 'bundle')} exec pumactl" }
13
13
  _cset(:puma_env) { fetch(:rack_env, fetch(:rails_env, 'production')) }
@@ -15,10 +15,16 @@ Capistrano::Configuration.instance.load do
15
15
  _cset(:puma_socket) { "unix://#{shared_path}/sockets/puma.sock" }
16
16
  _cset(:puma_role) { :app }
17
17
 
18
+ if fetch(:puma_default_hooks)
19
+ after 'deploy:stop', 'puma:stop'
20
+ after 'deploy:start', 'puma:start'
21
+ after 'deploy:restart', 'puma:restart'
22
+ end
23
+
18
24
  namespace :puma do
19
25
  desc 'Start puma'
20
26
  task :start, :roles => lambda { puma_role }, :on_no_matching_servers => :continue do
21
- run "cd #{current_path} && #{puma_cmd} #{start_options}", :pty => false
27
+ run "cd #{current_path} && #{puma_rails_additional_env} #{puma_cmd} #{start_options}", :pty => false
22
28
  end
23
29
 
24
30
  desc 'Stop puma'
@@ -29,7 +35,7 @@ Capistrano::Configuration.instance.load do
29
35
  desc 'Restart puma'
30
36
  task :restart, :roles => lambda { puma_role }, :on_no_matching_servers => :continue do
31
37
  begin
32
- run "cd #{current_path} && #{pumactl_cmd} -S #{state_path} restart"
38
+ run "cd #{current_path} && #{puma_rails_additional_env} #{pumactl_cmd} -S #{state_path} restart"
33
39
  rescue Capistrano::CommandError => ex
34
40
  puts "Failed to restart puma: #{ex}\nAssuming not started."
35
41
  start
@@ -38,7 +44,12 @@ Capistrano::Configuration.instance.load do
38
44
 
39
45
  desc 'Restart puma (phased restart)'
40
46
  task :phased_restart, :roles => lambda { puma_role }, :on_no_matching_servers => :continue do
41
- run "cd #{current_path} && #{pumactl_cmd} -S #{state_path} phased-restart"
47
+ begin
48
+ run "cd #{current_path} && #{puma_rails_additional_env} #{pumactl_cmd} -S #{state_path} phased-restart"
49
+ rescue Capistrano::CommandError => ex
50
+ puts "Failed to restart puma: #{ex}\nAssuming not started."
51
+ start
52
+ end
42
53
  end
43
54
 
44
55
  end
@@ -63,11 +74,17 @@ Capistrano::Configuration.instance.load do
63
74
  fetch(:rack_env, fetch(:rails_env, 'production'))
64
75
  end
65
76
 
77
+ #add additional env when start rails, such as : secret key, db username, db pwd or other what you want.
78
+ def puma_rails_additional_env
79
+ fetch(:puma_rails_additional_env, '')
80
+ end
81
+
66
82
  def state_path
67
83
  (config_file ? configuration.options[:state] : nil) || puma_state
68
84
  end
69
85
 
70
86
  def configuration
87
+ require 'puma'
71
88
  require 'puma/configuration'
72
89
 
73
90
  config = Puma::Configuration.new(:config_file => config_file)
data/lib/puma/cli.rb CHANGED
@@ -1,23 +1,22 @@
1
1
  require 'optparse'
2
2
  require 'uri'
3
3
 
4
- require 'puma/server'
5
- require 'puma/const'
6
- require 'puma/configuration'
7
- require 'puma/binder'
8
- require 'puma/detect'
9
- require 'puma/daemon_ext'
10
- require 'puma/util'
11
- require 'puma/single'
12
- require 'puma/cluster'
13
-
14
- require 'rack/commonlogger'
15
- require 'rack/utils'
4
+ require 'puma/launcher'
16
5
 
17
6
  module Puma
7
+ class << self
8
+ # The CLI exports its Puma::Configuration object here to allow
9
+ # apps to pick it up. An app needs to use it conditionally though
10
+ # since it is not set if the app is launched via another
11
+ # mechanism than the CLI class.
12
+ attr_accessor :cli_config
13
+ end
14
+
18
15
  # Handles invoke a Puma::Server in a command line style.
19
16
  #
20
17
  class CLI
18
+ KEYS_NOT_TO_PERSIST_IN_STATE = Launcher::KEYS_NOT_TO_PERSIST_IN_STATE
19
+
21
20
  # Create a new CLI object using +argv+ as the command line
22
21
  # arguments.
23
22
  #
@@ -26,497 +25,193 @@ module Puma
26
25
  #
27
26
  def initialize(argv, events=Events.stdio)
28
27
  @debug = false
29
- @argv = argv
28
+ @argv = argv.dup
30
29
 
31
30
  @events = events
32
31
 
33
- @status = nil
34
- @runner = nil
32
+ @conf = nil
35
33
 
36
- @config = nil
34
+ @stdout = nil
35
+ @stderr = nil
36
+ @append = false
37
37
 
38
- ENV['NEWRELIC_DISPATCHER'] ||= "puma"
38
+ @control_url = nil
39
+ @control_options = {}
39
40
 
40
41
  setup_options
41
- generate_restart_data
42
-
43
- @binder = Binder.new(@events)
44
- @binder.import_from_env
45
- end
46
-
47
- # The Binder object containing the sockets bound to.
48
- attr_reader :binder
49
-
50
- # The Configuration object used.
51
- attr_reader :config
52
-
53
- # The Hash of options used to configure puma.
54
- attr_reader :options
55
-
56
- # The Events object used to output information.
57
- attr_reader :events
58
-
59
- # Delegate +log+ to +@events+
60
- #
61
- def log(str)
62
- @events.log str
63
- end
64
-
65
- # Delegate +error+ to +@events+
66
- #
67
- def error(str)
68
- @events.error str
69
- end
70
-
71
- def debug(str)
72
- if @options[:debug]
73
- @events.log "- #{str}"
74
- end
75
- end
76
-
77
- def jruby?
78
- IS_JRUBY
79
- end
80
-
81
- def windows?
82
- RUBY_PLATFORM =~ /mswin32|ming32/
83
- end
84
-
85
- def unsupported(str, cond=true)
86
- return unless cond
87
- @events.error str
88
- raise UnsupportedOption
89
- end
90
-
91
- # Build the OptionParser object to handle the available options.
92
- #
93
- def setup_options
94
- @options = {
95
- :min_threads => 0,
96
- :max_threads => 16,
97
- :quiet => false,
98
- :debug => false,
99
- :binds => [],
100
- :workers => 0,
101
- :daemon => false,
102
- :worker_boot => []
103
- }
104
-
105
- @parser = OptionParser.new do |o|
106
- o.on "-b", "--bind URI", "URI to bind to (tcp://, unix://, ssl://)" do |arg|
107
- @options[:binds] << arg
108
- end
109
-
110
- o.on "-C", "--config PATH", "Load PATH as a config file" do |arg|
111
- @options[:config_file] = arg
112
- end
113
-
114
- o.on "--control URL", "The bind url to use for the control server",
115
- "Use 'auto' to use temp unix server" do |arg|
116
- if arg
117
- @options[:control_url] = arg
118
- elsif jruby?
119
- unsupported "No default url available on JRuby"
120
- end
121
- end
122
-
123
- o.on "--control-token TOKEN",
124
- "The token to use as authentication for the control server" do |arg|
125
- @options[:control_auth_token] = arg
126
- end
127
-
128
- o.on "-d", "--daemon", "Daemonize the server into the background" do
129
- @options[:daemon] = true
130
- @options[:quiet] = true
131
- end
132
-
133
- o.on "--debug", "Log lowlevel debugging information" do
134
- @options[:debug] = true
135
- end
136
-
137
- o.on "--dir DIR", "Change to DIR before starting" do |d|
138
- @options[:directory] = d.to_s
139
- @options[:worker_directory] = d.to_s
140
- end
141
-
142
- o.on "-e", "--environment ENVIRONMENT",
143
- "The environment to run the Rack app on (default development)" do |arg|
144
- @options[:environment] = arg
145
- end
146
-
147
- o.on "-I", "--include PATH", "Specify $LOAD_PATH directories" do |arg|
148
- $LOAD_PATH.unshift(*arg.split(':'))
149
- end
150
42
 
151
- o.on "-p", "--port PORT", "Define the TCP port to bind to",
152
- "Use -b for more advanced options" do |arg|
153
- @options[:binds] << "tcp://#{Configuration::DefaultTCPHost}:#{arg}"
154
- end
155
-
156
- o.on "--pidfile PATH", "Use PATH as a pidfile" do |arg|
157
- @options[:pidfile] = arg
158
- end
159
-
160
- o.on "--preload", "Preload the app. Cluster mode only" do
161
- @options[:preload_app] = true
162
- end
163
-
164
- o.on "-q", "--quiet", "Quiet down the output" do
165
- @options[:quiet] = true
166
- end
167
-
168
- o.on "-R", "--restart-cmd CMD",
169
- "The puma command to run during a hot restart",
170
- "Default: inferred" do |cmd|
171
- @options[:restart_cmd] = cmd
172
- end
173
-
174
- o.on "-S", "--state PATH", "Where to store the state details" do |arg|
175
- @options[:state] = arg
176
- end
43
+ begin
44
+ @parser.parse! @argv
177
45
 
178
- o.on '-t', '--threads INT', "min:max threads to use (default 0:16)" do |arg|
179
- min, max = arg.split(":")
180
- if max
181
- @options[:min_threads] = min
182
- @options[:max_threads] = max
183
- else
184
- @options[:min_threads] = 0
185
- @options[:max_threads] = arg
46
+ if file = @argv.shift
47
+ @conf.configure do |c|
48
+ c.rackup file
186
49
  end
187
50
  end
188
-
189
- o.on "--tcp-mode", "Run the app in raw TCP mode instead of HTTP mode" do
190
- @options[:mode] = :tcp
191
- end
192
-
193
- o.on "-V", "--version", "Print the version information" do
194
- puts "puma version #{Puma::Const::VERSION}"
195
- exit 1
196
- end
197
-
198
- o.on "-w", "--workers COUNT",
199
- "Activate cluster mode: How many worker processes to create" do |arg|
200
- @options[:workers] = arg.to_i
201
- end
202
-
203
- end
204
-
205
- @parser.banner = "puma <options> <rackup file>"
206
-
207
- @parser.on_tail "-h", "--help", "Show help" do
208
- log @parser
51
+ rescue UnsupportedOption
209
52
  exit 1
210
53
  end
211
- end
212
-
213
- def write_state
214
- write_pid
215
-
216
- require 'yaml'
217
-
218
- if path = @options[:state]
219
- state = { "pid" => Process.pid }
220
-
221
- cfg = @config.dup
222
-
223
- [ :logger, :worker_boot, :on_restart ].each { |o| cfg.options.delete o }
224
54
 
225
- state["config"] = cfg
226
-
227
- File.open(path, "w") do |f|
228
- f.write state.to_yaml
55
+ @conf.configure do |c|
56
+ if @stdout || @stderr
57
+ c.stdout_redirect @stdout, @stderr, @append
229
58
  end
230
- end
231
- end
232
59
 
233
- # If configured, write the pid of the current process out
234
- # to a file.
235
- #
236
- def write_pid
237
- if path = @options[:pidfile]
238
- File.open(path, "w") do |f|
239
- f.puts Process.pid
60
+ if @control_url
61
+ c.activate_control_app @control_url, @control_options
240
62
  end
241
-
242
- cur = Process.pid
243
-
244
- at_exit do
245
- if cur == Process.pid
246
- delete_pidfile
247
- end
248
- end
249
- end
250
- end
251
-
252
- def set_rack_environment
253
- # Try the user option first, then the environment variable,
254
- # finally default to development
255
-
256
- env = @options[:environment] ||
257
- ENV['RACK_ENV'] ||
258
- 'development'
259
-
260
- @options[:environment] = env
261
- ENV['RACK_ENV'] = env
262
- end
263
-
264
- def delete_pidfile
265
- if path = @options[:pidfile]
266
- File.unlink path if File.exists? path
267
- end
268
- end
269
-
270
- # :nodoc:
271
- def parse_options
272
- @parser.parse! @argv
273
-
274
- if @argv.last
275
- @options[:rackup] = @argv.shift
276
- end
277
-
278
- @config = Puma::Configuration.new @options
279
-
280
- # Advertise the Configuration
281
- Puma.cli_config = @config
282
-
283
- @config.load
284
-
285
- if clustered?
286
- unsupported "worker mode not supported on JRuby or Windows",
287
- jruby? || windows?
288
- end
289
-
290
- if @options[:daemon] and windows?
291
- unsupported "daemon mode not supported on Windows"
292
63
  end
293
- end
294
-
295
- def clustered?
296
- @options[:workers] > 0
297
- end
298
64
 
299
- def graceful_stop
300
- @runner.stop_blocked
301
- log "- Goodbye!"
65
+ @launcher = Puma::Launcher.new(@conf, :events => @events, :argv => argv)
302
66
  end
303
67
 
304
- def generate_restart_data
305
- # Use the same trick as unicorn, namely favor PWD because
306
- # it will contain an unresolved symlink, useful for when
307
- # the pwd is /data/releases/current.
308
- if dir = ENV['PWD']
309
- s_env = File.stat(dir)
310
- s_pwd = File.stat(Dir.pwd)
311
-
312
- if s_env.ino == s_pwd.ino and s_env.dev == s_pwd.dev
313
- @restart_dir = dir
314
- @options[:worker_directory] = dir
315
- end
316
- end
317
-
318
- @restart_dir ||= Dir.pwd
319
-
320
- @original_argv = ARGV.dup
321
-
322
- if defined? Rubinius::OS_ARGV
323
- @restart_argv = Rubinius::OS_ARGV
324
- else
325
- require 'rubygems'
326
-
327
- # if $0 is a file in the current directory, then restart
328
- # it the same, otherwise add -S on there because it was
329
- # picked up in PATH.
330
- #
331
- if File.exists?($0)
332
- arg0 = [Gem.ruby, $0]
333
- else
334
- arg0 = [Gem.ruby, "-S", $0]
335
- end
336
-
337
- # Detect and reinject -Ilib from the command line
338
- lib = File.expand_path "lib"
339
- arg0[1,0] = ["-I", lib] if $:[0] == lib
68
+ attr_reader :launcher
340
69
 
341
- @restart_argv = arg0 + ARGV
342
- end
70
+ # Parse the options, load the rackup, start the server and wait
71
+ # for it to finish.
72
+ #
73
+ def run
74
+ @launcher.run
343
75
  end
344
76
 
345
- def restart_args
346
- if cmd = @options[:restart_cmd]
347
- cmd.split(' ') + @original_argv
348
- else
349
- @restart_argv
350
- end
77
+ private
78
+ def unsupported(str)
79
+ @events.error(str)
80
+ raise UnsupportedOption
351
81
  end
352
82
 
353
- def jruby_daemon_start
354
- require 'puma/jruby_restart'
355
- JRubyRestart.daemon_start(@restart_dir, restart_args)
356
- end
83
+ # Build the OptionParser object to handle the available options.
84
+ #
357
85
 
358
- def restart!
359
- @options[:on_restart].each do |block|
360
- block.call self
361
- end
86
+ def setup_options
87
+ @conf = Configuration.new do |c|
88
+ @parser = OptionParser.new do |o|
89
+ o.on "-b", "--bind URI", "URI to bind to (tcp://, unix://, ssl://)" do |arg|
90
+ c.bind arg
91
+ end
362
92
 
363
- if jruby?
364
- @binder.listeners.each_with_index do |(str,io),i|
365
- io.close
93
+ o.on "-C", "--config PATH", "Load PATH as a config file" do |arg|
94
+ c.load arg
95
+ end
366
96
 
367
- # We have to unlink a unix socket path that's not being used
368
- uri = URI.parse str
369
- if uri.scheme == "unix"
370
- path = "#{uri.host}#{uri.path}"
371
- File.unlink path
97
+ o.on "--control URL", "The bind url to use for the control server",
98
+ "Use 'auto' to use temp unix server" do |arg|
99
+ if arg
100
+ @control_url = arg
101
+ elsif Puma.jruby?
102
+ unsupported "No default url available on JRuby"
103
+ end
372
104
  end
373
- end
374
105
 
375
- require 'puma/jruby_restart'
376
- JRubyRestart.chdir_exec(@restart_dir, restart_args)
106
+ o.on "--control-token TOKEN",
107
+ "The token to use as authentication for the control server" do |arg|
108
+ @control_options[:auth_token] = arg
109
+ end
377
110
 
378
- elsif windows?
379
- @binder.listeners.each_with_index do |(str,io),i|
380
- io.close
111
+ o.on "-d", "--daemon", "Daemonize the server into the background" do
112
+ c.daemonize
113
+ c.quiet
114
+ end
381
115
 
382
- # We have to unlink a unix socket path that's not being used
383
- uri = URI.parse str
384
- if uri.scheme == "unix"
385
- path = "#{uri.host}#{uri.path}"
386
- File.unlink path
116
+ o.on "--debug", "Log lowlevel debugging information" do
117
+ c.debug
387
118
  end
388
- end
389
119
 
390
- argv = restart_args
120
+ o.on "--dir DIR", "Change to DIR before starting" do |d|
121
+ c.directory d
122
+ end
391
123
 
392
- Dir.chdir @restart_dir
124
+ o.on "-e", "--environment ENVIRONMENT",
125
+ "The environment to run the Rack app on (default development)" do |arg|
126
+ c.environment arg
127
+ end
393
128
 
394
- argv += [redirects] unless RUBY_VERSION < '1.9'
395
- Kernel.exec(*argv)
129
+ o.on "-I", "--include PATH", "Specify $LOAD_PATH directories" do |arg|
130
+ $LOAD_PATH.unshift(*arg.split(':'))
131
+ end
396
132
 
397
- else
398
- redirects = {:close_others => true}
399
- @binder.listeners.each_with_index do |(l,io),i|
400
- ENV["PUMA_INHERIT_#{i}"] = "#{io.to_i}:#{l}"
401
- redirects[io.to_i] = io.to_i
402
- end
133
+ o.on "-p", "--port PORT", "Define the TCP port to bind to",
134
+ "Use -b for more advanced options" do |arg|
135
+ c.bind "tcp://#{Configuration::DefaultTCPHost}:#{arg}"
136
+ end
403
137
 
404
- argv = restart_args
138
+ o.on "--pidfile PATH", "Use PATH as a pidfile" do |arg|
139
+ c.pidfile arg
140
+ end
405
141
 
406
- Dir.chdir @restart_dir
142
+ o.on "--preload", "Preload the app. Cluster mode only" do
143
+ c.preload_app!
144
+ end
407
145
 
408
- argv += [redirects] unless RUBY_VERSION < '1.9'
409
- Kernel.exec(*argv)
410
- end
411
- end
146
+ o.on "--prune-bundler", "Prune out the bundler env if possible" do
147
+ c.prune_bundler
148
+ end
412
149
 
413
- # Parse the options, load the rackup, start the server and wait
414
- # for it to finish.
415
- #
416
- def run
417
- begin
418
- parse_options
419
- rescue UnsupportedOption
420
- exit 1
421
- end
150
+ o.on "-q", "--quiet", "Do not log requests internally (default true)" do
151
+ c.quiet
152
+ end
422
153
 
423
- if dir = @options[:directory]
424
- Dir.chdir dir
425
- end
154
+ o.on "-v", "--log-requests", "Log requests as they occur" do
155
+ c.log_requests
156
+ end
426
157
 
427
- set_rack_environment
158
+ o.on "-R", "--restart-cmd CMD",
159
+ "The puma command to run during a hot restart",
160
+ "Default: inferred" do |cmd|
161
+ c.restart_command cmd
162
+ end
428
163
 
429
- if clustered?
430
- @events = PidEvents.new STDOUT, STDERR
431
- @options[:logger] = @events
164
+ o.on "-S", "--state PATH", "Where to store the state details" do |arg|
165
+ c.state_path arg
166
+ end
432
167
 
433
- @runner = Cluster.new(self)
434
- else
435
- @runner = Single.new(self)
436
- end
168
+ o.on '-t', '--threads INT', "min:max threads to use (default 0:16)" do |arg|
169
+ min, max = arg.split(":")
170
+ if max
171
+ c.threads min, max
172
+ else
173
+ c.threads 0, max
174
+ end
175
+ end
437
176
 
438
- setup_signals
177
+ o.on "--tcp-mode", "Run the app in raw TCP mode instead of HTTP mode" do
178
+ c.tcp_mode!
179
+ end
439
180
 
440
- @status = :run
181
+ o.on "-V", "--version", "Print the version information" do
182
+ puts "puma version #{Puma::Const::VERSION}"
183
+ exit 0
184
+ end
441
185
 
442
- @runner.run
186
+ o.on "-w", "--workers COUNT",
187
+ "Activate cluster mode: How many worker processes to create" do |arg|
188
+ c.workers arg
189
+ end
443
190
 
444
- case @status
445
- when :halt
446
- log "* Stopping immediately!"
447
- when :run, :stop
448
- graceful_stop
449
- when :restart
450
- log "* Restarting..."
451
- @runner.before_restart
452
- restart!
453
- when :exit
454
- # nothing
455
- end
191
+ o.on "--tag NAME", "Additional text to display in process listing" do |arg|
192
+ c.tag arg
193
+ end
456
194
 
457
- ensure
458
- delete_pidfile
459
- end
195
+ o.on "--redirect-stdout FILE", "Redirect STDOUT to a specific file" do |arg|
196
+ @stdout = arg.to_s
197
+ end
460
198
 
461
- def setup_signals
462
- begin
463
- Signal.trap "SIGUSR2" do
464
- restart
465
- end
466
- rescue Exception
467
- log "*** SIGUSR2 not implemented, signal based restart unavailable!"
468
- end
199
+ o.on "--redirect-stderr FILE", "Redirect STDERR to a specific file" do |arg|
200
+ @stderr = arg.to_s
201
+ end
469
202
 
470
- begin
471
- Signal.trap "SIGUSR1" do
472
- phased_restart
473
- end
474
- rescue Exception
475
- log "*** SIGUSR1 not implemented, signal based restart unavailable!"
476
- end
203
+ o.on "--[no-]redirect-append", "Append to redirected files" do |val|
204
+ @append = val
205
+ end
477
206
 
478
- begin
479
- Signal.trap "SIGTERM" do
480
- stop
481
- end
482
- rescue Exception
483
- log "*** SIGTERM not implemented, signal based gracefully stopping unavailable!"
484
- end
207
+ o.banner = "puma <options> <rackup file>"
485
208
 
486
- if jruby?
487
- Signal.trap("INT") do
488
- @status = :exit
489
- graceful_stop
490
- exit
209
+ o.on_tail "-h", "--help", "Show help" do
210
+ $stdout.puts o
211
+ exit 0
212
+ end
491
213
  end
492
214
  end
493
215
  end
494
-
495
- def stop
496
- @status = :stop
497
- @runner.stop
498
- end
499
-
500
- def restart
501
- @status = :restart
502
- @runner.restart
503
- end
504
-
505
- def phased_restart
506
- unless @runner.respond_to?(:phased_restart) and @runner.phased_restart
507
- log "* phased-restart called but not available, restarting normally."
508
- return restart
509
- end
510
- true
511
- end
512
-
513
- def stats
514
- @runner.stats
515
- end
516
-
517
- def halt
518
- @status = :halt
519
- @runner.halt
520
- end
521
216
  end
522
217
  end