puma 3.12.0 → 5.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 (93) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +1413 -439
  3. data/LICENSE +23 -20
  4. data/README.md +131 -60
  5. data/bin/puma-wild +3 -9
  6. data/docs/architecture.md +24 -19
  7. data/docs/compile_options.md +19 -0
  8. data/docs/deployment.md +38 -13
  9. data/docs/fork_worker.md +33 -0
  10. data/docs/jungle/README.md +9 -0
  11. data/{tools → docs}/jungle/rc.d/README.md +1 -1
  12. data/{tools → docs}/jungle/rc.d/puma +2 -2
  13. data/{tools → docs}/jungle/rc.d/puma.conf +0 -0
  14. data/docs/kubernetes.md +66 -0
  15. data/docs/nginx.md +1 -1
  16. data/docs/plugins.md +20 -10
  17. data/docs/rails_dev_mode.md +29 -0
  18. data/docs/restart.md +47 -22
  19. data/docs/signals.md +7 -6
  20. data/docs/stats.md +142 -0
  21. data/docs/systemd.md +48 -70
  22. data/ext/puma_http11/PumaHttp11Service.java +2 -2
  23. data/ext/puma_http11/ext_help.h +1 -1
  24. data/ext/puma_http11/extconf.rb +27 -0
  25. data/ext/puma_http11/http11_parser.c +84 -109
  26. data/ext/puma_http11/http11_parser.h +1 -1
  27. data/ext/puma_http11/http11_parser.java.rl +22 -38
  28. data/ext/puma_http11/http11_parser.rl +4 -2
  29. data/ext/puma_http11/http11_parser_common.rl +3 -3
  30. data/ext/puma_http11/mini_ssl.c +262 -87
  31. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  32. data/ext/puma_http11/org/jruby/puma/Http11.java +108 -116
  33. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +89 -106
  34. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +92 -22
  35. data/ext/puma_http11/puma_http11.c +34 -50
  36. data/lib/puma/app/status.rb +68 -49
  37. data/lib/puma/binder.rb +197 -144
  38. data/lib/puma/cli.rb +17 -15
  39. data/lib/puma/client.rb +257 -226
  40. data/lib/puma/cluster/worker.rb +176 -0
  41. data/lib/puma/cluster/worker_handle.rb +90 -0
  42. data/lib/puma/cluster.rb +223 -212
  43. data/lib/puma/commonlogger.rb +4 -2
  44. data/lib/puma/configuration.rb +58 -51
  45. data/lib/puma/const.rb +41 -19
  46. data/lib/puma/control_cli.rb +117 -73
  47. data/lib/puma/detect.rb +26 -3
  48. data/lib/puma/dsl.rb +531 -123
  49. data/lib/puma/error_logger.rb +104 -0
  50. data/lib/puma/events.rb +57 -31
  51. data/lib/puma/io_buffer.rb +9 -5
  52. data/lib/puma/jruby_restart.rb +2 -58
  53. data/lib/puma/json.rb +96 -0
  54. data/lib/puma/launcher.rb +182 -70
  55. data/lib/puma/minissl/context_builder.rb +79 -0
  56. data/lib/puma/minissl.rb +149 -48
  57. data/lib/puma/null_io.rb +15 -1
  58. data/lib/puma/plugin/tmp_restart.rb +2 -0
  59. data/lib/puma/plugin.rb +8 -12
  60. data/lib/puma/queue_close.rb +26 -0
  61. data/lib/puma/rack/builder.rb +4 -5
  62. data/lib/puma/rack/urlmap.rb +2 -0
  63. data/lib/puma/rack_default.rb +2 -0
  64. data/lib/puma/reactor.rb +87 -316
  65. data/lib/puma/request.rb +456 -0
  66. data/lib/puma/runner.rb +33 -52
  67. data/lib/puma/server.rb +288 -679
  68. data/lib/puma/single.rb +13 -67
  69. data/lib/puma/state_file.rb +10 -3
  70. data/lib/puma/systemd.rb +46 -0
  71. data/lib/puma/thread_pool.rb +131 -81
  72. data/lib/puma/util.rb +14 -6
  73. data/lib/puma.rb +54 -0
  74. data/lib/rack/handler/puma.rb +8 -6
  75. data/tools/Dockerfile +16 -0
  76. data/tools/trickletest.rb +0 -1
  77. metadata +45 -29
  78. data/ext/puma_http11/io_buffer.c +0 -155
  79. data/lib/puma/accept_nonblock.rb +0 -23
  80. data/lib/puma/compat.rb +0 -14
  81. data/lib/puma/convenient.rb +0 -23
  82. data/lib/puma/daemon_ext.rb +0 -31
  83. data/lib/puma/delegation.rb +0 -11
  84. data/lib/puma/java_io_buffer.rb +0 -45
  85. data/lib/puma/rack/backports/uri/common_193.rb +0 -33
  86. data/lib/puma/tcp_logger.rb +0 -39
  87. data/tools/jungle/README.md +0 -19
  88. data/tools/jungle/init.d/README.md +0 -61
  89. data/tools/jungle/init.d/puma +0 -421
  90. data/tools/jungle/init.d/run-puma +0 -18
  91. data/tools/jungle/upstart/README.md +0 -61
  92. data/tools/jungle/upstart/puma-manager.conf +0 -31
  93. data/tools/jungle/upstart/puma.conf +0 -69
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'puma/const'
4
+
5
+ module Puma
6
+ # The implementation of a detailed error logging.
7
+ # @version 5.0.0
8
+ #
9
+ class ErrorLogger
10
+ include Const
11
+
12
+ attr_reader :ioerr
13
+
14
+ REQUEST_FORMAT = %{"%s %s%s" - (%s)}
15
+
16
+ def initialize(ioerr)
17
+ @ioerr = ioerr
18
+
19
+ @debug = ENV.key? 'PUMA_DEBUG'
20
+ end
21
+
22
+ def self.stdio
23
+ new $stderr
24
+ end
25
+
26
+ # Print occurred error details.
27
+ # +options+ hash with additional options:
28
+ # - +error+ is an exception object
29
+ # - +req+ the http request
30
+ # - +text+ (default nil) custom string to print in title
31
+ # and before all remaining info.
32
+ #
33
+ def info(options={})
34
+ log title(options)
35
+ end
36
+
37
+ # Print occurred error details only if
38
+ # environment variable PUMA_DEBUG is defined.
39
+ # +options+ hash with additional options:
40
+ # - +error+ is an exception object
41
+ # - +req+ the http request
42
+ # - +text+ (default nil) custom string to print in title
43
+ # and before all remaining info.
44
+ #
45
+ def debug(options={})
46
+ return unless @debug
47
+
48
+ error = options[:error]
49
+ req = options[:req]
50
+
51
+ string_block = []
52
+ string_block << title(options)
53
+ string_block << request_dump(req) if request_parsed?(req)
54
+ string_block << error.backtrace if error
55
+
56
+ log string_block.join("\n")
57
+ end
58
+
59
+ def title(options={})
60
+ text = options[:text]
61
+ req = options[:req]
62
+ error = options[:error]
63
+
64
+ string_block = ["#{Time.now}"]
65
+ string_block << " #{text}" if text
66
+ string_block << " (#{request_title(req)})" if request_parsed?(req)
67
+ string_block << ": #{error.inspect}" if error
68
+ string_block.join('')
69
+ end
70
+
71
+ def request_dump(req)
72
+ "Headers: #{request_headers(req)}\n" \
73
+ "Body: #{req.body}"
74
+ end
75
+
76
+ def request_title(req)
77
+ env = req.env
78
+
79
+ REQUEST_FORMAT % [
80
+ env[REQUEST_METHOD],
81
+ env[REQUEST_PATH] || env[PATH_INFO],
82
+ env[QUERY_STRING] || "",
83
+ env[HTTP_X_FORWARDED_FOR] || env[REMOTE_ADDR] || "-"
84
+ ]
85
+ end
86
+
87
+ def request_headers(req)
88
+ headers = req.env.select { |key, _| key.start_with?('HTTP_') }
89
+ headers.map { |key, value| [key[5..-1], value] }.to_h.inspect
90
+ end
91
+
92
+ def request_parsed?(req)
93
+ req && req.env[REQUEST_METHOD]
94
+ end
95
+
96
+ private
97
+
98
+ def log(str)
99
+ ioerr.puts str
100
+
101
+ ioerr.flush unless ioerr.sync
102
+ end
103
+ end
104
+ end
data/lib/puma/events.rb CHANGED
@@ -1,5 +1,7 @@
1
- require 'puma/const'
1
+ # frozen_string_literal: true
2
+
2
3
  require "puma/null_io"
4
+ require 'puma/error_logger'
3
5
  require 'stringio'
4
6
 
5
7
  module Puma
@@ -21,8 +23,6 @@ module Puma
21
23
  end
22
24
  end
23
25
 
24
- include Const
25
-
26
26
  # Create an Events object that prints to +stdout+ and +stderr+.
27
27
  #
28
28
  def initialize(stdout, stderr)
@@ -30,10 +30,8 @@ module Puma
30
30
  @stdout = stdout
31
31
  @stderr = stderr
32
32
 
33
- @stdout.sync = true
34
- @stderr.sync = true
35
-
36
33
  @debug = ENV.key? 'PUMA_DEBUG'
34
+ @error_logger = ErrorLogger.new(@stderr)
37
35
 
38
36
  @hooks = Hash.new { |h,k| h[k] = [] }
39
37
  end
@@ -64,7 +62,10 @@ module Puma
64
62
  # Write +str+ to +@stdout+
65
63
  #
66
64
  def log(str)
67
- @stdout.puts format(str)
65
+ @stdout.puts format(str) if @stdout.respond_to? :puts
66
+
67
+ @stdout.flush unless @stdout.sync
68
+ rescue Errno::EPIPE
68
69
  end
69
70
 
70
71
  def write(str)
@@ -78,7 +79,7 @@ module Puma
78
79
  # Write +str+ to +@stderr+
79
80
  #
80
81
  def error(str)
81
- @stderr.puts format("ERROR: #{str}")
82
+ @error_logger.info(text: format("ERROR: #{str}"))
82
83
  exit 1
83
84
  end
84
85
 
@@ -86,50 +87,75 @@ module Puma
86
87
  formatter.call(str)
87
88
  end
88
89
 
90
+ # An HTTP connection error has occurred.
91
+ # +error+ a connection exception, +req+ the request,
92
+ # and +text+ additional info
93
+ # @version 5.0.0
94
+ #
95
+ def connection_error(error, req, text="HTTP connection error")
96
+ @error_logger.info(error: error, req: req, text: text)
97
+ end
98
+
89
99
  # An HTTP parse error has occurred.
90
- # +server+ is the Server object, +env+ the request, and +error+ a
91
- # parsing exception.
100
+ # +error+ a parsing exception,
101
+ # and +req+ the request.
92
102
  #
93
- def parse_error(server, env, error)
94
- @stderr.puts "#{Time.now}: HTTP parse error, malformed request (#{env[HTTP_X_FORWARDED_FOR] || env[REMOTE_ADDR]}): #{error.inspect}\n---\n"
103
+ def parse_error(error, req)
104
+ @error_logger.info(error: error, req: req, text: 'HTTP parse error, malformed request')
95
105
  end
96
106
 
97
107
  # An SSL error has occurred.
98
- # +server+ is the Server object, +peeraddr+ peer address, +peercert+
99
- # any peer certificate (if present), and +error+ an exception object.
108
+ # @param error <Puma::MiniSSL::SSLError>
109
+ # @param ssl_socket <Puma::MiniSSL::Socket>
100
110
  #
101
- def ssl_error(server, peeraddr, peercert, error)
111
+ def ssl_error(error, ssl_socket)
112
+ peeraddr = ssl_socket.peeraddr.last rescue "<unknown>"
113
+ peercert = ssl_socket.peercert
102
114
  subject = peercert ? peercert.subject : nil
103
- @stderr.puts "#{Time.now}: SSL error, peer: #{peeraddr}, peer cert: #{subject}, #{error.inspect}"
115
+ @error_logger.info(error: error, text: "SSL error, peer: #{peeraddr}, peer cert: #{subject}")
104
116
  end
105
117
 
106
118
  # An unknown error has occurred.
107
- # +server+ is the Server object, +error+ an exception object,
108
- # +kind+ some additional info, and +env+ the request.
119
+ # +error+ an exception object, +req+ the request,
120
+ # and +text+ additional info
109
121
  #
110
- def unknown_error(server, error, kind="Unknown", env=nil)
111
- if error.respond_to? :render
112
- error.render "#{Time.now}: #{kind} error", @stderr
113
- else
114
- if env
115
- string_block = [ "#{Time.now}: #{kind} error handling request { #{env['REQUEST_METHOD']} #{env['PATH_INFO']} }" ]
116
- string_block << error.inspect
117
- else
118
- string_block = [ "#{Time.now}: #{kind} error: #{error.inspect}" ]
119
- end
120
- string_block << error.backtrace
121
- @stderr.puts string_block.join("\n")
122
- end
122
+ def unknown_error(error, req=nil, text="Unknown error")
123
+ @error_logger.info(error: error, req: req, text: text)
124
+ end
125
+
126
+ # Log occurred error debug dump.
127
+ # +error+ an exception object, +req+ the request,
128
+ # and +text+ additional info
129
+ # @version 5.0.0
130
+ #
131
+ def debug_error(error, req=nil, text="")
132
+ @error_logger.debug(error: error, req: req, text: text)
123
133
  end
124
134
 
125
135
  def on_booted(&block)
126
136
  register(:on_booted, &block)
127
137
  end
128
138
 
139
+ def on_restart(&block)
140
+ register(:on_restart, &block)
141
+ end
142
+
143
+ def on_stopped(&block)
144
+ register(:on_stopped, &block)
145
+ end
146
+
129
147
  def fire_on_booted!
130
148
  fire(:on_booted)
131
149
  end
132
150
 
151
+ def fire_on_restart!
152
+ fire(:on_restart)
153
+ end
154
+
155
+ def fire_on_stopped!
156
+ fire(:on_stopped)
157
+ end
158
+
133
159
  DEFAULT = new(STDOUT, STDERR)
134
160
 
135
161
  # Returns an Events object which writes its status to 2 StringIO
@@ -1,7 +1,11 @@
1
- require 'puma/detect'
1
+ # frozen_string_literal: true
2
2
 
3
- if Puma.jruby?
4
- require 'puma/java_io_buffer'
5
- else
6
- require 'puma/puma_http11'
3
+ module Puma
4
+ class IOBuffer < String
5
+ def append(*args)
6
+ args.each { |a| concat(a) }
7
+ end
8
+
9
+ alias reset clear
10
+ end
7
11
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'ffi'
2
4
 
3
5
  module Puma
@@ -20,63 +22,5 @@ module Puma
20
22
  execlp(cmd, *argv)
21
23
  raise SystemCallError.new(FFI.errno)
22
24
  end
23
-
24
- PermKey = 'PUMA_DAEMON_PERM'
25
- RestartKey = 'PUMA_DAEMON_RESTART'
26
-
27
- # Called to tell things "Your now always in daemon mode,
28
- # don't try to reenter it."
29
- #
30
- def self.perm_daemonize
31
- ENV[PermKey] = "1"
32
- end
33
-
34
- def self.daemon?
35
- ENV.key?(PermKey) || ENV.key?(RestartKey)
36
- end
37
-
38
- def self.daemon_init
39
- return true if ENV.key?(PermKey)
40
-
41
- return false unless ENV.key? RestartKey
42
-
43
- master = ENV[RestartKey]
44
-
45
- # In case the master disappears early
46
- begin
47
- Process.kill "SIGUSR2", master.to_i
48
- rescue SystemCallError => e
49
- end
50
-
51
- ENV[RestartKey] = ""
52
-
53
- setsid
54
-
55
- null = File.open "/dev/null", "w+"
56
- STDIN.reopen null
57
- STDOUT.reopen null
58
- STDERR.reopen null
59
-
60
- true
61
- end
62
-
63
- def self.daemon_start(dir, argv)
64
- ENV[RestartKey] = Process.pid.to_s
65
-
66
- if k = ENV['PUMA_JRUBY_DAEMON_OPTS']
67
- ENV['JRUBY_OPTS'] = k
68
- end
69
-
70
- cmd = argv.first
71
- argv = ([:string] * argv.size).zip(argv).flatten
72
- argv << :string
73
- argv << nil
74
-
75
- chdir(dir)
76
- ret = fork
77
- return ret if ret != 0
78
- execlp(cmd, *argv)
79
- raise SystemCallError.new(FFI.errno)
80
- end
81
25
  end
82
26
  end
data/lib/puma/json.rb ADDED
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+ require 'stringio'
3
+
4
+ module Puma
5
+
6
+ # Puma deliberately avoids the use of the json gem and instead performs JSON
7
+ # serialization without any external dependencies. In a puma cluster, loading
8
+ # any gem into the puma master process means that operators cannot use a
9
+ # phased restart to upgrade their application if the new version of that
10
+ # application uses a different version of that gem. The json gem in
11
+ # particular is additionally problematic because it leverages native
12
+ # extensions. If the puma master process relies on a gem with native
13
+ # extensions and operators remove gems from disk related to old releases,
14
+ # subsequent phased restarts can fail.
15
+ #
16
+ # The implementation of JSON serialization in this module is not designed to
17
+ # be particularly full-featured or fast. It just has to handle the few places
18
+ # where Puma relies on JSON serialization internally.
19
+
20
+ module JSON
21
+ QUOTE = /"/
22
+ BACKSLASH = /\\/
23
+ CONTROL_CHAR_TO_ESCAPE = /[\x00-\x1F]/ # As required by ECMA-404
24
+ CHAR_TO_ESCAPE = Regexp.union QUOTE, BACKSLASH, CONTROL_CHAR_TO_ESCAPE
25
+
26
+ class SerializationError < StandardError; end
27
+
28
+ class << self
29
+ def generate(value)
30
+ StringIO.open do |io|
31
+ serialize_value io, value
32
+ io.string
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def serialize_value(output, value)
39
+ case value
40
+ when Hash
41
+ output << '{'
42
+ value.each_with_index do |(k, v), index|
43
+ output << ',' if index != 0
44
+ serialize_object_key output, k
45
+ output << ':'
46
+ serialize_value output, v
47
+ end
48
+ output << '}'
49
+ when Array
50
+ output << '['
51
+ value.each_with_index do |member, index|
52
+ output << ',' if index != 0
53
+ serialize_value output, member
54
+ end
55
+ output << ']'
56
+ when Integer, Float
57
+ output << value.to_s
58
+ when String
59
+ serialize_string output, value
60
+ when true
61
+ output << 'true'
62
+ when false
63
+ output << 'false'
64
+ when nil
65
+ output << 'null'
66
+ else
67
+ raise SerializationError, "Unexpected value of type #{value.class}"
68
+ end
69
+ end
70
+
71
+ def serialize_string(output, value)
72
+ output << '"'
73
+ output << value.gsub(CHAR_TO_ESCAPE) do |character|
74
+ case character
75
+ when BACKSLASH
76
+ '\\\\'
77
+ when QUOTE
78
+ '\\"'
79
+ when CONTROL_CHAR_TO_ESCAPE
80
+ '\u%.4X' % character.ord
81
+ end
82
+ end
83
+ output << '"'
84
+ end
85
+
86
+ def serialize_object_key(output, value)
87
+ case value
88
+ when Symbol, String
89
+ serialize_string output, value.to_s
90
+ else
91
+ raise SerializationError, "Could not serialize object of type #{value.class} as object key"
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end