puma 2.16.0 → 3.11.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. checksums.yaml +5 -5
  2. data/{History.txt → History.md} +489 -70
  3. data/README.md +143 -174
  4. data/docs/architecture.md +36 -0
  5. data/{DEPLOYMENT.md → docs/deployment.md} +1 -1
  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/nginx.md +2 -2
  10. data/docs/plugins.md +28 -0
  11. data/docs/restart.md +39 -0
  12. data/docs/signals.md +56 -3
  13. data/docs/systemd.md +272 -0
  14. data/ext/puma_http11/extconf.rb +2 -0
  15. data/ext/puma_http11/http11_parser.c +291 -447
  16. data/ext/puma_http11/http11_parser.h +1 -0
  17. data/ext/puma_http11/http11_parser.java.rl +5 -5
  18. data/ext/puma_http11/http11_parser.rl +10 -9
  19. data/ext/puma_http11/http11_parser_common.rl +1 -1
  20. data/ext/puma_http11/io_buffer.c +8 -8
  21. data/ext/puma_http11/mini_ssl.c +64 -6
  22. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +113 -131
  23. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +9 -2
  24. data/ext/puma_http11/puma_http11.c +1 -0
  25. data/lib/puma/app/status.rb +9 -1
  26. data/lib/puma/binder.rb +90 -38
  27. data/lib/puma/cli.rb +134 -491
  28. data/lib/puma/client.rb +142 -4
  29. data/lib/puma/cluster.rb +132 -76
  30. data/lib/puma/commonlogger.rb +19 -20
  31. data/lib/puma/compat.rb +3 -7
  32. data/lib/puma/configuration.rb +206 -67
  33. data/lib/puma/const.rb +21 -31
  34. data/lib/puma/control_cli.rb +92 -103
  35. data/lib/puma/convenient.rb +23 -0
  36. data/lib/puma/daemon_ext.rb +6 -0
  37. data/lib/puma/detect.rb +10 -1
  38. data/lib/puma/dsl.rb +203 -45
  39. data/lib/puma/events.rb +22 -13
  40. data/lib/puma/io_buffer.rb +1 -1
  41. data/lib/puma/jruby_restart.rb +1 -2
  42. data/lib/puma/launcher.rb +431 -0
  43. data/lib/puma/minissl.rb +83 -4
  44. data/lib/puma/null_io.rb +19 -11
  45. data/lib/puma/plugin/tmp_restart.rb +34 -0
  46. data/lib/puma/plugin.rb +115 -0
  47. data/lib/puma/rack/backports/uri/common_193.rb +17 -13
  48. data/lib/puma/rack/builder.rb +3 -0
  49. data/lib/puma/rack/urlmap.rb +9 -8
  50. data/lib/puma/reactor.rb +18 -0
  51. data/lib/puma/runner.rb +43 -15
  52. data/lib/puma/server.rb +141 -35
  53. data/lib/puma/single.rb +16 -6
  54. data/lib/puma/state_file.rb +29 -0
  55. data/lib/puma/tcp_logger.rb +8 -1
  56. data/lib/puma/thread_pool.rb +60 -10
  57. data/lib/puma/util.rb +1 -5
  58. data/lib/puma.rb +13 -4
  59. data/lib/rack/handler/puma.rb +76 -29
  60. data/tools/jungle/README.md +12 -2
  61. data/tools/jungle/init.d/README.md +9 -2
  62. data/tools/jungle/init.d/puma +86 -59
  63. data/tools/jungle/init.d/run-puma +16 -1
  64. data/tools/jungle/rc.d/README.md +74 -0
  65. data/tools/jungle/rc.d/puma +61 -0
  66. data/tools/jungle/rc.d/puma.conf +10 -0
  67. data/tools/jungle/upstart/puma.conf +1 -1
  68. data/tools/trickletest.rb +1 -1
  69. metadata +28 -95
  70. data/COPYING +0 -55
  71. data/Gemfile +0 -13
  72. data/Manifest.txt +0 -74
  73. data/Rakefile +0 -158
  74. data/docs/config.md +0 -0
  75. data/lib/puma/capistrano.rb +0 -94
  76. data/lib/puma/rack/backports/uri/common_18.rb +0 -56
  77. data/lib/puma/rack/backports/uri/common_192.rb +0 -52
  78. data/puma.gemspec +0 -52
@@ -0,0 +1,115 @@
1
+ module Puma
2
+ class UnknownPlugin < RuntimeError; end
3
+
4
+ class PluginLoader
5
+ def initialize
6
+ @instances = []
7
+ end
8
+
9
+ def create(name)
10
+ if cls = Plugins.find(name)
11
+ plugin = cls.new(Plugin)
12
+ @instances << plugin
13
+ return plugin
14
+ end
15
+
16
+ raise UnknownPlugin, "File failed to register properly named plugin"
17
+ end
18
+
19
+ def fire_starts(launcher)
20
+ @instances.each do |i|
21
+ if i.respond_to? :start
22
+ i.start(launcher)
23
+ end
24
+ end
25
+ end
26
+ end
27
+
28
+ class PluginRegistry
29
+ def initialize
30
+ @plugins = {}
31
+ @background = []
32
+ end
33
+
34
+ def register(name, cls)
35
+ @plugins[name] = cls
36
+ end
37
+
38
+ def find(name)
39
+ name = name.to_s
40
+
41
+ if cls = @plugins[name]
42
+ return cls
43
+ end
44
+
45
+ begin
46
+ require "puma/plugin/#{name}"
47
+ rescue LoadError
48
+ raise UnknownPlugin, "Unable to find plugin: #{name}"
49
+ end
50
+
51
+ if cls = @plugins[name]
52
+ return cls
53
+ end
54
+
55
+ raise UnknownPlugin, "file failed to register a plugin"
56
+ end
57
+
58
+ def add_background(blk)
59
+ @background << blk
60
+ end
61
+
62
+ def fire_background
63
+ @background.each do |b|
64
+ Thread.new(&b)
65
+ end
66
+ end
67
+ end
68
+
69
+ Plugins = PluginRegistry.new
70
+
71
+ class Plugin
72
+ # Matches
73
+ # "C:/Ruby22/lib/ruby/gems/2.2.0/gems/puma-3.0.1/lib/puma/plugin/tmp_restart.rb:3:in `<top (required)>'"
74
+ # AS
75
+ # C:/Ruby22/lib/ruby/gems/2.2.0/gems/puma-3.0.1/lib/puma/plugin/tmp_restart.rb
76
+ CALLER_FILE = /
77
+ \A # start of string
78
+ .+ # file path (one or more characters)
79
+ (?= # stop previous match when
80
+ :\d+ # a colon is followed by one or more digits
81
+ :in # followed by a colon followed by in
82
+ )
83
+ /x
84
+
85
+ def self.extract_name(ary)
86
+ path = ary.first[CALLER_FILE]
87
+
88
+ m = %r!puma/plugin/([^/]*)\.rb$!.match(path)
89
+ return m[1]
90
+ end
91
+
92
+ def self.create(&blk)
93
+ name = extract_name(caller)
94
+
95
+ cls = Class.new(self)
96
+
97
+ cls.class_eval(&blk)
98
+
99
+ Plugins.register name, cls
100
+ end
101
+
102
+ def initialize(loader)
103
+ @loader = loader
104
+ end
105
+
106
+ def in_background(&blk)
107
+ Plugins.add_background blk
108
+ end
109
+
110
+ def workers_supported?
111
+ return false if Puma.jruby? || Puma.windows?
112
+ true
113
+ end
114
+ end
115
+ end
@@ -8,22 +8,26 @@ require 'uri/common'
8
8
  # Relevant commit:
9
9
  # https://github.com/ruby/ruby/commit/edb7cdf1eabaff78dfa5ffedfbc2e91b29fa9ca1
10
10
 
11
+
11
12
  module URI
12
- 256.times do |i|
13
- TBLENCWWWCOMP_[i.chr] = '%%%02X' % i
14
- end
15
- TBLENCWWWCOMP_[' '] = '+'
16
- TBLENCWWWCOMP_.freeze
13
+ begin
14
+ 256.times do |i|
15
+ TBLENCWWWCOMP_[i.chr] = '%%%02X' % i
16
+ end
17
+ TBLENCWWWCOMP_[' '] = '+'
18
+ TBLENCWWWCOMP_.freeze
17
19
 
18
- 256.times do |i|
19
- h, l = i>>4, i&15
20
- TBLDECWWWCOMP_['%%%X%X' % [h, l]] = i.chr
21
- TBLDECWWWCOMP_['%%%x%X' % [h, l]] = i.chr
22
- TBLDECWWWCOMP_['%%%X%x' % [h, l]] = i.chr
23
- TBLDECWWWCOMP_['%%%x%x' % [h, l]] = i.chr
20
+ 256.times do |i|
21
+ h, l = i>>4, i&15
22
+ TBLDECWWWCOMP_['%%%X%X' % [h, l]] = i.chr
23
+ TBLDECWWWCOMP_['%%%x%X' % [h, l]] = i.chr
24
+ TBLDECWWWCOMP_['%%%X%x' % [h, l]] = i.chr
25
+ TBLDECWWWCOMP_['%%%x%x' % [h, l]] = i.chr
26
+ end
27
+ TBLDECWWWCOMP_['+'] = ' '
28
+ TBLDECWWWCOMP_.freeze
29
+ rescue Exception
24
30
  end
25
- TBLDECWWWCOMP_['+'] = ' '
26
- TBLDECWWWCOMP_.freeze
27
31
  end
28
32
 
29
33
  # :startdoc:
@@ -1,3 +1,6 @@
1
+ module Puma
2
+ end
3
+
1
4
  module Puma::Rack
2
5
  class Options
3
6
  def parse!(args)
@@ -43,15 +43,17 @@ module Puma::Rack
43
43
  def call(env)
44
44
  path = env['PATH_INFO']
45
45
  script_name = env['SCRIPT_NAME']
46
- hHost = env['HTTP_HOST']
47
- sName = env['SERVER_NAME']
48
- sPort = env['SERVER_PORT']
46
+ http_host = env['HTTP_HOST']
47
+ server_name = env['SERVER_NAME']
48
+ server_port = env['SERVER_PORT']
49
+
50
+ is_same_server = casecmp?(http_host, server_name) ||
51
+ casecmp?(http_host, "#{server_name}:#{server_port}")
49
52
 
50
53
  @mapping.each do |host, location, match, app|
51
- unless casecmp?(hHost, host) \
52
- || casecmp?(sName, host) \
53
- || (!host && (casecmp?(hHost, sName) ||
54
- casecmp?(hHost, sName+':'+sPort)))
54
+ unless casecmp?(http_host, host) \
55
+ || casecmp?(server_name, host) \
56
+ || (!host && is_same_server)
55
57
  next
56
58
  end
57
59
 
@@ -87,4 +89,3 @@ module Puma::Rack
87
89
  end
88
90
  end
89
91
  end
90
-
data/lib/puma/reactor.rb CHANGED
@@ -28,6 +28,7 @@ module Puma
28
28
  begin
29
29
  ready = IO.select sockets, nil, nil, @sleep_for
30
30
  rescue IOError => e
31
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
31
32
  if sockets.any? { |socket| socket.closed? }
32
33
  STDERR.puts "Error in select: #{e.message} (#{e.class})"
33
34
  STDERR.puts e.backtrace
@@ -75,8 +76,19 @@ module Puma
75
76
  sockets.delete c
76
77
  end
77
78
 
79
+ # Don't report these to the lowlevel_error handler, otherwise
80
+ # will be flooding them with errors when persistent connections
81
+ # are closed.
82
+ rescue ConnectionError
83
+ c.write_500
84
+ c.close
85
+
86
+ sockets.delete c
87
+
78
88
  # SSL handshake failure
79
89
  rescue MiniSSL::SSLError => e
90
+ @server.lowlevel_error(e, c.env)
91
+
80
92
  ssl_socket = c.io
81
93
  addr = ssl_socket.peeraddr.last
82
94
  cert = ssl_socket.peercert
@@ -88,6 +100,8 @@ module Puma
88
100
 
89
101
  # The client doesn't know HTTP well
90
102
  rescue HttpParserError => e
103
+ @server.lowlevel_error(e, c.env)
104
+
91
105
  c.write_400
92
106
  c.close
93
107
 
@@ -95,6 +109,8 @@ module Puma
95
109
 
96
110
  @events.parse_error @server, c.env, e
97
111
  rescue StandardError => e
112
+ @server.lowlevel_error(e, c.env)
113
+
98
114
  c.write_500
99
115
  c.close
100
116
 
@@ -180,6 +196,7 @@ module Puma
180
196
  begin
181
197
  @trigger << "c"
182
198
  rescue IOError
199
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
183
200
  end
184
201
  end
185
202
 
@@ -187,6 +204,7 @@ module Puma
187
204
  begin
188
205
  @trigger << "!"
189
206
  rescue IOError
207
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
190
208
  end
191
209
 
192
210
  @thread.join
data/lib/puma/runner.rb CHANGED
@@ -1,7 +1,11 @@
1
+ require 'puma/server'
2
+ require 'puma/const'
3
+
1
4
  module Puma
2
5
  class Runner
3
- def initialize(cli)
4
- @cli = cli
6
+ def initialize(cli, events)
7
+ @launcher = cli
8
+ @events = events
5
9
  @options = cli.options
6
10
  @app = nil
7
11
  @control = nil
@@ -16,15 +20,19 @@ module Puma
16
20
  end
17
21
 
18
22
  def log(str)
19
- @cli.log str
23
+ @events.log str
24
+ end
25
+
26
+ def before_restart
27
+ @control.stop(true) if @control
20
28
  end
21
29
 
22
30
  def error(str)
23
- @cli.error str
31
+ @events.error str
24
32
  end
25
33
 
26
- def before_restart
27
- @control.stop(true) if @control
34
+ def debug(str)
35
+ @events.log "- #{str}" if @options[:debug]
28
36
  end
29
37
 
30
38
  def start_control
@@ -35,13 +43,13 @@ module Puma
35
43
 
36
44
  uri = URI.parse str
37
45
 
38
- app = Puma::App::Status.new @cli
46
+ app = Puma::App::Status.new @launcher
39
47
 
40
48
  if token = @options[:control_auth_token]
41
49
  app.auth_token = token unless token.empty? or token == :none
42
50
  end
43
51
 
44
- control = Puma::Server.new app, @cli.events
52
+ control = Puma::Server.new app, @launcher.events
45
53
  control.min_threads = 0
46
54
  control.max_threads = 1
47
55
 
@@ -67,7 +75,11 @@ module Puma
67
75
  if !defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby"
68
76
  "ruby #{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}"
69
77
  else
70
- "#{RUBY_ENGINE} #{RUBY_VERSION}"
78
+ if defined?(RUBY_ENGINE_VERSION)
79
+ "#{RUBY_ENGINE} #{RUBY_ENGINE_VERSION} - ruby #{RUBY_VERSION}"
80
+ else
81
+ "#{RUBY_ENGINE} #{RUBY_VERSION}"
82
+ end
71
83
  end
72
84
  end
73
85
 
@@ -85,18 +97,30 @@ module Puma
85
97
  end
86
98
  end
87
99
 
100
+ def redirected_io?
101
+ @options[:redirect_stdout] || @options[:redirect_stderr]
102
+ end
103
+
88
104
  def redirect_io
89
105
  stdout = @options[:redirect_stdout]
90
106
  stderr = @options[:redirect_stderr]
91
107
  append = @options[:redirect_append]
92
108
 
93
109
  if stdout
110
+ unless Dir.exist?(File.dirname(stdout))
111
+ raise "Cannot redirect STDOUT to #{stdout}"
112
+ end
113
+
94
114
  STDOUT.reopen stdout, (append ? "a" : "w")
95
115
  STDOUT.sync = true
96
116
  STDOUT.puts "=== puma startup: #{Time.now} ==="
97
117
  end
98
118
 
99
119
  if stderr
120
+ unless Dir.exist?(File.dirname(stderr))
121
+ raise "Cannot redirect STDERR to #{stderr}"
122
+ end
123
+
100
124
  STDERR.reopen stderr, (append ? "a" : "w")
101
125
  STDERR.sync = true
102
126
  STDERR.puts "=== puma startup: #{Time.now} ==="
@@ -104,39 +128,43 @@ module Puma
104
128
  end
105
129
 
106
130
  def load_and_bind
107
- unless @cli.config.app_configured?
131
+ unless @launcher.config.app_configured?
108
132
  error "No application configured, nothing to run"
109
133
  exit 1
110
134
  end
111
135
 
112
136
  # Load the app before we daemonize.
113
137
  begin
114
- @app = @cli.config.app
138
+ @app = @launcher.config.app
115
139
  rescue Exception => e
116
140
  log "! Unable to load application: #{e.class}: #{e.message}"
117
141
  raise e
118
142
  end
119
143
 
120
- @cli.binder.parse @options[:binds], self
144
+ @launcher.binder.parse @options[:binds], self
121
145
  end
122
146
 
123
147
  def app
124
- @app ||= @cli.config.app
148
+ @app ||= @launcher.config.app
125
149
  end
126
150
 
127
151
  def start_server
128
152
  min_t = @options[:min_threads]
129
153
  max_t = @options[:max_threads]
130
154
 
131
- server = Puma::Server.new app, @cli.events, @options
155
+ server = Puma::Server.new app, @launcher.events, @options
132
156
  server.min_threads = min_t
133
157
  server.max_threads = max_t
134
- server.inherit_binder @cli.binder
158
+ server.inherit_binder @launcher.binder
135
159
 
136
160
  if @options[:mode] == :tcp
137
161
  server.tcp_mode!
138
162
  end
139
163
 
164
+ if @options[:early_hints]
165
+ server.early_hints = true
166
+ end
167
+
140
168
  unless development?
141
169
  server.leak_stack_on_error = false
142
170
  end