puma 3.12.1 → 5.6.7

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 (94) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +1608 -447
  3. data/LICENSE +23 -20
  4. data/README.md +175 -63
  5. data/bin/puma-wild +3 -9
  6. data/docs/architecture.md +59 -21
  7. data/docs/compile_options.md +21 -0
  8. data/docs/deployment.md +69 -58
  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/docs/kubernetes.md +66 -0
  14. data/docs/nginx.md +1 -1
  15. data/docs/plugins.md +22 -12
  16. data/docs/rails_dev_mode.md +28 -0
  17. data/docs/restart.md +47 -22
  18. data/docs/signals.md +13 -11
  19. data/docs/stats.md +142 -0
  20. data/docs/systemd.md +95 -120
  21. data/ext/puma_http11/PumaHttp11Service.java +2 -2
  22. data/ext/puma_http11/ext_help.h +1 -1
  23. data/ext/puma_http11/extconf.rb +57 -2
  24. data/ext/puma_http11/http11_parser.c +105 -117
  25. data/ext/puma_http11/http11_parser.h +1 -1
  26. data/ext/puma_http11/http11_parser.java.rl +22 -38
  27. data/ext/puma_http11/http11_parser.rl +4 -2
  28. data/ext/puma_http11/http11_parser_common.rl +4 -4
  29. data/ext/puma_http11/mini_ssl.c +339 -98
  30. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  31. data/ext/puma_http11/org/jruby/puma/Http11.java +108 -116
  32. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +84 -99
  33. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +124 -71
  34. data/ext/puma_http11/puma_http11.c +35 -51
  35. data/lib/puma/app/status.rb +71 -49
  36. data/lib/puma/binder.rb +234 -137
  37. data/lib/puma/cli.rb +28 -18
  38. data/lib/puma/client.rb +350 -230
  39. data/lib/puma/cluster/worker.rb +173 -0
  40. data/lib/puma/cluster/worker_handle.rb +94 -0
  41. data/lib/puma/cluster.rb +247 -232
  42. data/lib/puma/commonlogger.rb +2 -2
  43. data/lib/puma/configuration.rb +61 -51
  44. data/lib/puma/const.rb +42 -21
  45. data/lib/puma/control_cli.rb +115 -67
  46. data/lib/puma/detect.rb +29 -2
  47. data/lib/puma/dsl.rb +619 -123
  48. data/lib/puma/error_logger.rb +104 -0
  49. data/lib/puma/events.rb +55 -31
  50. data/lib/puma/io_buffer.rb +7 -5
  51. data/lib/puma/jruby_restart.rb +0 -58
  52. data/lib/puma/json_serialization.rb +96 -0
  53. data/lib/puma/launcher.rb +193 -69
  54. data/lib/puma/minissl/context_builder.rb +81 -0
  55. data/lib/puma/minissl.rb +170 -65
  56. data/lib/puma/null_io.rb +18 -1
  57. data/lib/puma/plugin/tmp_restart.rb +2 -0
  58. data/lib/puma/plugin.rb +7 -13
  59. data/lib/puma/queue_close.rb +26 -0
  60. data/lib/puma/rack/builder.rb +3 -5
  61. data/lib/puma/rack/urlmap.rb +2 -0
  62. data/lib/puma/rack_default.rb +2 -0
  63. data/lib/puma/reactor.rb +85 -316
  64. data/lib/puma/request.rb +476 -0
  65. data/lib/puma/runner.rb +48 -55
  66. data/lib/puma/server.rb +305 -695
  67. data/lib/puma/single.rb +11 -67
  68. data/lib/puma/state_file.rb +48 -8
  69. data/lib/puma/systemd.rb +46 -0
  70. data/lib/puma/thread_pool.rb +132 -82
  71. data/lib/puma/util.rb +33 -10
  72. data/lib/puma.rb +56 -0
  73. data/lib/rack/handler/puma.rb +5 -6
  74. data/lib/rack/version_restriction.rb +15 -0
  75. data/tools/Dockerfile +16 -0
  76. data/tools/trickletest.rb +0 -1
  77. metadata +46 -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 -25
  82. data/lib/puma/daemon_ext.rb +0 -33
  83. data/lib/puma/delegation.rb +0 -13
  84. data/lib/puma/java_io_buffer.rb +0 -47
  85. data/lib/puma/rack/backports/uri/common_193.rb +0 -33
  86. data/lib/puma/tcp_logger.rb +0 -41
  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
  94. /data/{tools → docs}/jungle/rc.d/puma.conf +0 -0
@@ -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,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'puma/const'
4
3
  require "puma/null_io"
4
+ require 'puma/error_logger'
5
5
  require 'stringio'
6
6
 
7
7
  module Puma
@@ -23,8 +23,6 @@ module Puma
23
23
  end
24
24
  end
25
25
 
26
- include Const
27
-
28
26
  # Create an Events object that prints to +stdout+ and +stderr+.
29
27
  #
30
28
  def initialize(stdout, stderr)
@@ -32,10 +30,8 @@ module Puma
32
30
  @stdout = stdout
33
31
  @stderr = stderr
34
32
 
35
- @stdout.sync = true
36
- @stderr.sync = true
37
-
38
33
  @debug = ENV.key? 'PUMA_DEBUG'
34
+ @error_logger = ErrorLogger.new(@stderr)
39
35
 
40
36
  @hooks = Hash.new { |h,k| h[k] = [] }
41
37
  end
@@ -66,7 +62,10 @@ module Puma
66
62
  # Write +str+ to +@stdout+
67
63
  #
68
64
  def log(str)
69
- @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
70
69
  end
71
70
 
72
71
  def write(str)
@@ -80,7 +79,7 @@ module Puma
80
79
  # Write +str+ to +@stderr+
81
80
  #
82
81
  def error(str)
83
- @stderr.puts format("ERROR: #{str}")
82
+ @error_logger.info(text: format("ERROR: #{str}"))
84
83
  exit 1
85
84
  end
86
85
 
@@ -88,50 +87,75 @@ module Puma
88
87
  formatter.call(str)
89
88
  end
90
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
+
91
99
  # An HTTP parse error has occurred.
92
- # +server+ is the Server object, +env+ the request, and +error+ a
93
- # parsing exception.
100
+ # +error+ a parsing exception,
101
+ # and +req+ the request.
94
102
  #
95
- def parse_error(server, env, error)
96
- @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')
97
105
  end
98
106
 
99
107
  # An SSL error has occurred.
100
- # +server+ is the Server object, +peeraddr+ peer address, +peercert+
101
- # any peer certificate (if present), and +error+ an exception object.
108
+ # @param error <Puma::MiniSSL::SSLError>
109
+ # @param ssl_socket <Puma::MiniSSL::Socket>
102
110
  #
103
- 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
104
114
  subject = peercert ? peercert.subject : nil
105
- @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}")
106
116
  end
107
117
 
108
118
  # An unknown error has occurred.
109
- # +server+ is the Server object, +error+ an exception object,
110
- # +kind+ some additional info, and +env+ the request.
119
+ # +error+ an exception object, +req+ the request,
120
+ # and +text+ additional info
111
121
  #
112
- def unknown_error(server, error, kind="Unknown", env=nil)
113
- if error.respond_to? :render
114
- error.render "#{Time.now}: #{kind} error", @stderr
115
- else
116
- if env
117
- string_block = [ "#{Time.now}: #{kind} error handling request { #{env['REQUEST_METHOD']} #{env['PATH_INFO']} }" ]
118
- string_block << error.inspect
119
- else
120
- string_block = [ "#{Time.now}: #{kind} error: #{error.inspect}" ]
121
- end
122
- string_block << error.backtrace
123
- @stderr.puts string_block.join("\n")
124
- 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)
125
133
  end
126
134
 
127
135
  def on_booted(&block)
128
136
  register(:on_booted, &block)
129
137
  end
130
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
+
131
147
  def fire_on_booted!
132
148
  fire(:on_booted)
133
149
  end
134
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
+
135
159
  DEFAULT = new(STDOUT, STDERR)
136
160
 
137
161
  # Returns an Events object which writes its status to 2 StringIO
@@ -1,9 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'puma/detect'
3
+ module Puma
4
+ class IOBuffer < String
5
+ def append(*args)
6
+ args.each { |a| concat(a) }
7
+ end
4
8
 
5
- if Puma.jruby?
6
- require 'puma/java_io_buffer'
7
- else
8
- require 'puma/puma_http11'
9
+ alias reset clear
10
+ end
9
11
  end
@@ -22,63 +22,5 @@ module Puma
22
22
  execlp(cmd, *argv)
23
23
  raise SystemCallError.new(FFI.errno)
24
24
  end
25
-
26
- PermKey = 'PUMA_DAEMON_PERM'
27
- RestartKey = 'PUMA_DAEMON_RESTART'
28
-
29
- # Called to tell things "Your now always in daemon mode,
30
- # don't try to reenter it."
31
- #
32
- def self.perm_daemonize
33
- ENV[PermKey] = "1"
34
- end
35
-
36
- def self.daemon?
37
- ENV.key?(PermKey) || ENV.key?(RestartKey)
38
- end
39
-
40
- def self.daemon_init
41
- return true if ENV.key?(PermKey)
42
-
43
- return false unless ENV.key? RestartKey
44
-
45
- master = ENV[RestartKey]
46
-
47
- # In case the master disappears early
48
- begin
49
- Process.kill "SIGUSR2", master.to_i
50
- rescue SystemCallError => e
51
- end
52
-
53
- ENV[RestartKey] = ""
54
-
55
- setsid
56
-
57
- null = File.open "/dev/null", "w+"
58
- STDIN.reopen null
59
- STDOUT.reopen null
60
- STDERR.reopen null
61
-
62
- true
63
- end
64
-
65
- def self.daemon_start(dir, argv)
66
- ENV[RestartKey] = Process.pid.to_s
67
-
68
- if k = ENV['PUMA_JRUBY_DAEMON_OPTS']
69
- ENV['JRUBY_OPTS'] = k
70
- end
71
-
72
- cmd = argv.first
73
- argv = ([:string] * argv.size).zip(argv).flatten
74
- argv << :string
75
- argv << nil
76
-
77
- chdir(dir)
78
- ret = fork
79
- return ret if ret != 0
80
- execlp(cmd, *argv)
81
- raise SystemCallError.new(FFI.errno)
82
- end
83
25
  end
84
26
  end
@@ -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 JSONSerialization
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