puma 3.9.1 → 4.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 (82) hide show
  1. checksums.yaml +5 -5
  2. data/History.md +232 -0
  3. data/README.md +162 -224
  4. data/docs/architecture.md +37 -0
  5. data/{DEPLOYMENT.md → docs/deployment.md} +24 -4
  6. data/docs/images/puma-connection-flow-no-reactor.png +0 -0
  7. data/docs/images/puma-connection-flow.png +0 -0
  8. data/docs/images/puma-general-arch.png +0 -0
  9. data/docs/plugins.md +38 -0
  10. data/docs/restart.md +41 -0
  11. data/docs/signals.md +56 -3
  12. data/docs/systemd.md +130 -37
  13. data/docs/tcp_mode.md +96 -0
  14. data/ext/puma_http11/PumaHttp11Service.java +2 -0
  15. data/ext/puma_http11/extconf.rb +13 -0
  16. data/ext/puma_http11/http11_parser.c +115 -140
  17. data/ext/puma_http11/http11_parser.java.rl +21 -37
  18. data/ext/puma_http11/http11_parser.rl +9 -9
  19. data/ext/puma_http11/http11_parser_common.rl +3 -3
  20. data/ext/puma_http11/mini_ssl.c +104 -8
  21. data/ext/puma_http11/org/jruby/puma/Http11.java +106 -114
  22. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +90 -108
  23. data/ext/puma_http11/org/jruby/puma/IOBuffer.java +72 -0
  24. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +21 -4
  25. data/ext/puma_http11/puma_http11.c +2 -0
  26. data/lib/puma.rb +16 -0
  27. data/lib/puma/accept_nonblock.rb +7 -1
  28. data/lib/puma/app/status.rb +40 -26
  29. data/lib/puma/binder.rb +57 -74
  30. data/lib/puma/cli.rb +26 -7
  31. data/lib/puma/client.rb +243 -190
  32. data/lib/puma/cluster.rb +78 -34
  33. data/lib/puma/commonlogger.rb +2 -0
  34. data/lib/puma/configuration.rb +24 -16
  35. data/lib/puma/const.rb +36 -18
  36. data/lib/puma/control_cli.rb +46 -19
  37. data/lib/puma/detect.rb +2 -0
  38. data/lib/puma/dsl.rb +329 -68
  39. data/lib/puma/events.rb +6 -1
  40. data/lib/puma/io_buffer.rb +3 -6
  41. data/lib/puma/jruby_restart.rb +2 -1
  42. data/lib/puma/launcher.rb +120 -58
  43. data/lib/puma/minissl.rb +69 -27
  44. data/lib/puma/minissl/context_builder.rb +76 -0
  45. data/lib/puma/null_io.rb +2 -0
  46. data/lib/puma/plugin.rb +7 -2
  47. data/lib/puma/plugin/tmp_restart.rb +2 -1
  48. data/lib/puma/rack/builder.rb +4 -1
  49. data/lib/puma/rack/urlmap.rb +2 -0
  50. data/lib/puma/rack_default.rb +2 -0
  51. data/lib/puma/reactor.rb +224 -34
  52. data/lib/puma/runner.rb +25 -4
  53. data/lib/puma/server.rb +148 -62
  54. data/lib/puma/single.rb +16 -5
  55. data/lib/puma/state_file.rb +2 -0
  56. data/lib/puma/tcp_logger.rb +2 -0
  57. data/lib/puma/thread_pool.rb +61 -38
  58. data/lib/puma/util.rb +2 -6
  59. data/lib/rack/handler/puma.rb +10 -4
  60. data/tools/docker/Dockerfile +16 -0
  61. data/tools/jungle/README.md +12 -2
  62. data/tools/jungle/init.d/README.md +2 -0
  63. data/tools/jungle/init.d/puma +8 -8
  64. data/tools/jungle/init.d/run-puma +1 -1
  65. data/tools/jungle/rc.d/README.md +74 -0
  66. data/tools/jungle/rc.d/puma +61 -0
  67. data/tools/jungle/rc.d/puma.conf +10 -0
  68. data/tools/trickletest.rb +1 -2
  69. metadata +29 -56
  70. data/.github/issue_template.md +0 -20
  71. data/Gemfile +0 -14
  72. data/Manifest.txt +0 -78
  73. data/Rakefile +0 -165
  74. data/Release.md +0 -9
  75. data/gemfiles/2.1-Gemfile +0 -12
  76. data/lib/puma/compat.rb +0 -14
  77. data/lib/puma/convenient.rb +0 -23
  78. data/lib/puma/daemon_ext.rb +0 -31
  79. data/lib/puma/delegation.rb +0 -11
  80. data/lib/puma/java_io_buffer.rb +0 -45
  81. data/lib/puma/rack/backports/uri/common_193.rb +0 -33
  82. data/puma.gemspec +0 -20
@@ -200,6 +200,8 @@ void http_field(puma_parser* hp, const char *field, size_t flen,
200
200
  f = rb_str_new(hp->buf, new_size);
201
201
  }
202
202
 
203
+ while (vlen > 0 && isspace(value[vlen - 1])) vlen--;
204
+
203
205
  /* check for duplicate header */
204
206
  v = rb_hash_aref(hp->request, f);
205
207
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Standard libraries
2
4
  require 'socket'
3
5
  require 'tempfile'
@@ -12,4 +14,18 @@ module Puma
12
14
  autoload :Const, 'puma/const'
13
15
  autoload :Server, 'puma/server'
14
16
  autoload :Launcher, 'puma/launcher'
17
+
18
+ def self.stats_object=(val)
19
+ @get_stats = val
20
+ end
21
+
22
+ def self.stats
23
+ @get_stats.stats
24
+ end
25
+
26
+ # Thread name is new in Ruby 2.3
27
+ def self.set_thread_name(name)
28
+ return unless Thread.current.respond_to?(:name=)
29
+ Thread.current.name = "puma #{name}"
30
+ end
15
31
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'openssl'
2
4
 
3
5
  module OpenSSL
@@ -13,7 +15,11 @@ module OpenSSL
13
15
  ssl.accept if @start_immediately
14
16
  ssl
15
17
  rescue SSLError => ex
16
- sock.close
18
+ if ssl
19
+ ssl.close
20
+ else
21
+ sock.close
22
+ end
17
23
  raise ex
18
24
  end
19
25
  end
@@ -1,26 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
1
5
  module Puma
2
6
  module App
7
+ # Check out {#call}'s source code to see what actions this web application
8
+ # can respond to.
3
9
  class Status
4
- def initialize(cli)
5
- @cli = cli
6
- @auth_token = nil
7
- end
8
10
  OK_STATUS = '{ "status": "ok" }'.freeze
9
11
 
10
- attr_accessor :auth_token
11
-
12
- def authenticate(env)
13
- return true unless @auth_token
14
- env['QUERY_STRING'].to_s.split(/&;/).include?("token=#{@auth_token}")
15
- end
16
-
17
- def rack_response(status, body, content_type='application/json')
18
- headers = {
19
- 'Content-Type' => content_type,
20
- 'Content-Length' => body.bytesize.to_s
21
- }
22
-
23
- [status, headers, [body]]
12
+ def initialize(cli, token = nil)
13
+ @cli = cli
14
+ @auth_token = token
24
15
  end
25
16
 
26
17
  def call(env)
@@ -31,36 +22,59 @@ module Puma
31
22
  case env['PATH_INFO']
32
23
  when /\/stop$/
33
24
  @cli.stop
34
- return rack_response(200, OK_STATUS)
25
+ rack_response(200, OK_STATUS)
35
26
 
36
27
  when /\/halt$/
37
28
  @cli.halt
38
- return rack_response(200, OK_STATUS)
29
+ rack_response(200, OK_STATUS)
39
30
 
40
31
  when /\/restart$/
41
32
  @cli.restart
42
- return rack_response(200, OK_STATUS)
33
+ rack_response(200, OK_STATUS)
43
34
 
44
35
  when /\/phased-restart$/
45
36
  if !@cli.phased_restart
46
- return rack_response(404, '{ "error": "phased restart not available" }')
37
+ rack_response(404, '{ "error": "phased restart not available" }')
47
38
  else
48
- return rack_response(200, OK_STATUS)
39
+ rack_response(200, OK_STATUS)
49
40
  end
50
41
 
51
42
  when /\/reload-worker-directory$/
52
43
  if !@cli.send(:reload_worker_directory)
53
- return rack_response(404, '{ "error": "reload_worker_directory not available" }')
44
+ rack_response(404, '{ "error": "reload_worker_directory not available" }')
54
45
  else
55
- return rack_response(200, OK_STATUS)
46
+ rack_response(200, OK_STATUS)
56
47
  end
57
48
 
49
+ when /\/gc$/
50
+ GC.start
51
+ rack_response(200, OK_STATUS)
52
+
53
+ when /\/gc-stats$/
54
+ rack_response(200, GC.stat.to_json)
55
+
58
56
  when /\/stats$/
59
- return rack_response(200, @cli.stats)
57
+ rack_response(200, @cli.stats)
60
58
  else
61
59
  rack_response 404, "Unsupported action", 'text/plain'
62
60
  end
63
61
  end
62
+
63
+ private
64
+
65
+ def authenticate(env)
66
+ return true unless @auth_token
67
+ env['QUERY_STRING'].to_s.split(/&;/).include?("token=#{@auth_token}")
68
+ end
69
+
70
+ def rack_response(status, body, content_type='application/json')
71
+ headers = {
72
+ 'Content-Type' => content_type,
73
+ 'Content-Length' => body.bytesize.to_s
74
+ }
75
+
76
+ [status, headers, [body]]
77
+ end
64
78
  end
65
79
  end
66
80
  end
@@ -1,8 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'uri'
2
4
  require 'socket'
3
5
 
4
6
  require 'puma/const'
5
7
  require 'puma/util'
8
+ require 'puma/minissl/context_builder'
6
9
 
7
10
  module Puma
8
11
  class Binder
@@ -40,7 +43,7 @@ module Puma
40
43
  @ios = []
41
44
  end
42
45
 
43
- attr_reader :listeners, :ios
46
+ attr_reader :ios
44
47
 
45
48
  def env(sock)
46
49
  @envs.fetch(sock, @proto_env)
@@ -48,7 +51,6 @@ module Puma
48
51
 
49
52
  def close
50
53
  @ios.each { |i| i.close }
51
- @unix_paths.each { |i| File.unlink i }
52
54
  end
53
55
 
54
56
  def import_from_env
@@ -90,19 +92,29 @@ module Puma
90
92
  case uri.scheme
91
93
  when "tcp"
92
94
  if fd = @inherited_fds.delete(str)
93
- logger.log "* Inherited #{str}"
94
95
  io = inherit_tcp_listener uri.host, uri.port, fd
96
+ logger.log "* Inherited #{str}"
95
97
  elsif sock = @activated_sockets.delete([ :tcp, uri.host, uri.port ])
96
- logger.log "* Activated #{str}"
97
98
  io = inherit_tcp_listener uri.host, uri.port, sock
99
+ logger.log "* Activated #{str}"
98
100
  else
99
101
  params = Util.parse_query uri.query
100
102
 
101
103
  opt = params.key?('low_latency')
102
104
  bak = params.fetch('backlog', 1024).to_i
103
105
 
104
- logger.log "* Listening on #{str}"
105
106
  io = add_tcp_listener uri.host, uri.port, opt, bak
107
+
108
+ @ios.each do |i|
109
+ next unless TCPServer === i
110
+ addr = if i.local_address.ipv6?
111
+ "[#{i.local_address.ip_unpack[0]}]:#{i.local_address.ip_unpack[1]}"
112
+ else
113
+ i.local_address.ip_unpack.join(':')
114
+ end
115
+
116
+ logger.log "* Listening on tcp://#{addr}"
117
+ end
106
118
  end
107
119
 
108
120
  @listeners << [str, io] if io
@@ -110,17 +122,15 @@ module Puma
110
122
  path = "#{uri.host}#{uri.path}".gsub("%20", " ")
111
123
 
112
124
  if fd = @inherited_fds.delete(str)
113
- logger.log "* Inherited #{str}"
114
125
  io = inherit_unix_listener path, fd
126
+ logger.log "* Inherited #{str}"
115
127
  elsif sock = @activated_sockets.delete([ :unix, path ])
116
- logger.log "* Activated #{str}"
117
128
  io = inherit_unix_listener path, sock
129
+ logger.log "* Activated #{str}"
118
130
  else
119
- logger.log "* Listening on #{str}"
120
-
121
131
  umask = nil
122
132
  mode = nil
123
- backlog = nil
133
+ backlog = 1024
124
134
 
125
135
  if uri.query
126
136
  params = Util.parse_query uri.query
@@ -139,74 +149,23 @@ module Puma
139
149
  end
140
150
 
141
151
  io = add_unix_listener path, umask, mode, backlog
152
+ logger.log "* Listening on #{str}"
142
153
  end
143
154
 
144
155
  @listeners << [str, io]
145
156
  when "ssl"
146
157
  params = Util.parse_query uri.query
147
- require 'puma/minissl'
148
-
149
- MiniSSL.check
150
-
151
- ctx = MiniSSL::Context.new
152
-
153
- if defined?(JRUBY_VERSION)
154
- unless params['keystore']
155
- @events.error "Please specify the Java keystore via 'keystore='"
156
- end
157
-
158
- ctx.keystore = params['keystore']
159
-
160
- unless params['keystore-pass']
161
- @events.error "Please specify the Java keystore password via 'keystore-pass='"
162
- end
163
-
164
- ctx.keystore_pass = params['keystore-pass']
165
- else
166
- unless params['key']
167
- @events.error "Please specify the SSL key via 'key='"
168
- end
169
-
170
- ctx.key = params['key']
171
-
172
- unless params['cert']
173
- @events.error "Please specify the SSL cert via 'cert='"
174
- end
175
-
176
- ctx.cert = params['cert']
177
-
178
- if ['peer', 'force_peer'].include?(params['verify_mode'])
179
- unless params['ca']
180
- @events.error "Please specify the SSL ca via 'ca='"
181
- end
182
- end
183
-
184
- ctx.ca = params['ca'] if params['ca']
185
- end
186
-
187
- if params['verify_mode']
188
- ctx.verify_mode = case params['verify_mode']
189
- when "peer"
190
- MiniSSL::VERIFY_PEER
191
- when "force_peer"
192
- MiniSSL::VERIFY_PEER | MiniSSL::VERIFY_FAIL_IF_NO_PEER_CERT
193
- when "none"
194
- MiniSSL::VERIFY_NONE
195
- else
196
- @events.error "Please specify a valid verify_mode="
197
- MiniSSL::VERIFY_NONE
198
- end
199
- end
158
+ ctx = MiniSSL::ContextBuilder.new(params, @events).context
200
159
 
201
160
  if fd = @inherited_fds.delete(str)
202
161
  logger.log "* Inherited #{str}"
203
162
  io = inherit_ssl_listener fd, ctx
204
163
  elsif sock = @activated_sockets.delete([ :tcp, uri.host, uri.port ])
205
- logger.log "* Activated #{str}"
206
164
  io = inherit_ssl_listener sock, ctx
165
+ logger.log "* Activated #{str}"
207
166
  else
208
- logger.log "* Listening on #{str}"
209
167
  io = add_ssl_listener uri.host, uri.port, ctx
168
+ logger.log "* Listening on #{str}"
210
169
  end
211
170
 
212
171
  @listeners << [str, io] if io
@@ -245,9 +204,10 @@ module Puma
245
204
  end
246
205
  end
247
206
 
248
- def localhost_addresses
249
- addrs = TCPSocket.gethostbyname "localhost"
250
- addrs[3..-1].uniq
207
+ def loopback_addresses
208
+ Socket.ip_address_list.select do |addrinfo|
209
+ addrinfo.ipv6_loopback? || addrinfo.ipv4_loopback?
210
+ end.map { |addrinfo| addrinfo.ip_address }.uniq
251
211
  end
252
212
 
253
213
  # Tell the server to listen on host +host+, port +port+.
@@ -259,7 +219,7 @@ module Puma
259
219
  #
260
220
  def add_tcp_listener(host, port, optimize_for_latency=true, backlog=1024)
261
221
  if host == "localhost"
262
- localhost_addresses.each do |addr|
222
+ loopback_addresses.each do |addr|
263
223
  add_tcp_listener addr, port, optimize_for_latency, backlog
264
224
  end
265
225
  return
@@ -298,7 +258,7 @@ module Puma
298
258
  MiniSSL.check
299
259
 
300
260
  if host == "localhost"
301
- localhost_addresses.each do |addr|
261
+ loopback_addresses.each do |addr|
302
262
  add_ssl_listener addr, port, ctx, optimize_for_latency, backlog
303
263
  end
304
264
  return
@@ -312,6 +272,7 @@ module Puma
312
272
  s.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
313
273
  s.listen backlog
314
274
 
275
+
315
276
  ssl = MiniSSL::Server.new s, ctx
316
277
  env = @proto_env.dup
317
278
  env[HTTPS_KEY] = HTTPS
@@ -343,8 +304,8 @@ module Puma
343
304
 
344
305
  # Tell the server to listen on +path+ as a UNIX domain socket.
345
306
  #
346
- def add_unix_listener(path, umask=nil, mode=nil, backlog=nil)
347
- @unix_paths << path
307
+ def add_unix_listener(path, umask=nil, mode=nil, backlog=1024)
308
+ @unix_paths << path unless File.exist? path
348
309
 
349
310
  # Let anyone connect by default
350
311
  umask ||= 0
@@ -364,7 +325,7 @@ module Puma
364
325
  end
365
326
 
366
327
  s = UNIXServer.new(path)
367
- s.listen backlog if backlog
328
+ s.listen backlog
368
329
  @ios << s
369
330
  ensure
370
331
  File.umask old_mask
@@ -382,7 +343,7 @@ module Puma
382
343
  end
383
344
 
384
345
  def inherit_unix_listener(path, fd)
385
- @unix_paths << path
346
+ @unix_paths << path unless File.exist? path
386
347
 
387
348
  if fd.kind_of? TCPServer
388
349
  s = fd
@@ -398,5 +359,27 @@ module Puma
398
359
  s
399
360
  end
400
361
 
362
+ def close_listeners
363
+ @listeners.each do |l, io|
364
+ io.close
365
+ uri = URI.parse(l)
366
+ next unless uri.scheme == 'unix'
367
+ unix_path = "#{uri.host}#{uri.path}"
368
+ File.unlink unix_path if @unix_paths.include? unix_path
369
+ end
370
+ end
371
+
372
+ def close_unix_paths
373
+ @unix_paths.each { |up| File.unlink(up) if File.exist? up }
374
+ end
375
+
376
+ def redirects_for_restart
377
+ redirects = {:close_others => true}
378
+ @listeners.each_with_index do |(l, io), i|
379
+ ENV["PUMA_INHERIT_#{i}"] = "#{io.to_i}:#{l}"
380
+ redirects[io.to_i] = io.to_i
381
+ end
382
+ redirects
383
+ end
401
384
  end
402
385
  end
@@ -1,6 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'optparse'
2
4
  require 'uri'
3
5
 
6
+ require 'puma'
4
7
  require 'puma/configuration'
5
8
  require 'puma/launcher'
6
9
  require 'puma/const'
@@ -83,6 +86,14 @@ module Puma
83
86
  raise UnsupportedOption
84
87
  end
85
88
 
89
+ def configure_control_url(command_line_arg)
90
+ if command_line_arg
91
+ @control_url = command_line_arg
92
+ elsif Puma.jruby?
93
+ unsupported "No default url available on JRuby"
94
+ end
95
+ end
96
+
86
97
  # Build the OptionParser object to handle the available options.
87
98
  #
88
99
 
@@ -97,13 +108,13 @@ module Puma
97
108
  file_config.load arg
98
109
  end
99
110
 
100
- o.on "--control URL", "The bind url to use for the control server",
101
- "Use 'auto' to use temp unix server" do |arg|
102
- if arg
103
- @control_url = arg
104
- elsif Puma.jruby?
105
- unsupported "No default url available on JRuby"
106
- end
111
+ o.on "--control-url URL", "The bind url to use for the control server. Use 'auto' to use temp unix server" do |arg|
112
+ configure_control_url(arg)
113
+ end
114
+
115
+ # alias --control-url for backwards-compatibility
116
+ o.on "--control URL", "DEPRECATED alias for --control-url" do |arg|
117
+ configure_control_url(arg)
107
118
  end
108
119
 
109
120
  o.on "--control-token TOKEN",
@@ -150,6 +161,10 @@ module Puma
150
161
  user_config.prune_bundler
151
162
  end
152
163
 
164
+ o.on "--extra-runtime-dependencies GEM1,GEM2", "Defines any extra needed gems when using --prune-bundler" do |arg|
165
+ user_config.extra_runtime_dependencies arg.split(',')
166
+ end
167
+
153
168
  o.on "-q", "--quiet", "Do not log requests internally (default true)" do
154
169
  user_config.quiet
155
170
  end
@@ -181,6 +196,10 @@ module Puma
181
196
  user_config.tcp_mode!
182
197
  end
183
198
 
199
+ o.on "--early-hints", "Enable early hints support" do
200
+ user_config.early_hints
201
+ end
202
+
184
203
  o.on "-V", "--version", "Print the version information" do
185
204
  puts "puma version #{Puma::Const::VERSION}"
186
205
  exit 0