puma 4.2.0

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 (80) hide show
  1. checksums.yaml +7 -0
  2. data/History.md +1513 -0
  3. data/LICENSE +26 -0
  4. data/README.md +309 -0
  5. data/bin/puma +10 -0
  6. data/bin/puma-wild +31 -0
  7. data/bin/pumactl +12 -0
  8. data/docs/architecture.md +37 -0
  9. data/docs/deployment.md +111 -0
  10. data/docs/images/puma-connection-flow-no-reactor.png +0 -0
  11. data/docs/images/puma-connection-flow.png +0 -0
  12. data/docs/images/puma-general-arch.png +0 -0
  13. data/docs/nginx.md +80 -0
  14. data/docs/plugins.md +28 -0
  15. data/docs/restart.md +41 -0
  16. data/docs/signals.md +96 -0
  17. data/docs/systemd.md +290 -0
  18. data/ext/puma_http11/PumaHttp11Service.java +19 -0
  19. data/ext/puma_http11/ext_help.h +15 -0
  20. data/ext/puma_http11/extconf.rb +23 -0
  21. data/ext/puma_http11/http11_parser.c +1044 -0
  22. data/ext/puma_http11/http11_parser.h +65 -0
  23. data/ext/puma_http11/http11_parser.java.rl +161 -0
  24. data/ext/puma_http11/http11_parser.rl +147 -0
  25. data/ext/puma_http11/http11_parser_common.rl +54 -0
  26. data/ext/puma_http11/io_buffer.c +155 -0
  27. data/ext/puma_http11/mini_ssl.c +553 -0
  28. data/ext/puma_http11/org/jruby/puma/Http11.java +234 -0
  29. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +470 -0
  30. data/ext/puma_http11/org/jruby/puma/IOBuffer.java +72 -0
  31. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +363 -0
  32. data/ext/puma_http11/puma_http11.c +500 -0
  33. data/lib/puma.rb +31 -0
  34. data/lib/puma/accept_nonblock.rb +29 -0
  35. data/lib/puma/app/status.rb +80 -0
  36. data/lib/puma/binder.rb +439 -0
  37. data/lib/puma/cli.rb +239 -0
  38. data/lib/puma/client.rb +494 -0
  39. data/lib/puma/cluster.rb +555 -0
  40. data/lib/puma/commonlogger.rb +108 -0
  41. data/lib/puma/configuration.rb +362 -0
  42. data/lib/puma/const.rb +235 -0
  43. data/lib/puma/control_cli.rb +281 -0
  44. data/lib/puma/convenient.rb +25 -0
  45. data/lib/puma/delegation.rb +13 -0
  46. data/lib/puma/detect.rb +15 -0
  47. data/lib/puma/dsl.rb +738 -0
  48. data/lib/puma/events.rb +156 -0
  49. data/lib/puma/io_buffer.rb +4 -0
  50. data/lib/puma/jruby_restart.rb +84 -0
  51. data/lib/puma/launcher.rb +478 -0
  52. data/lib/puma/minissl.rb +278 -0
  53. data/lib/puma/null_io.rb +44 -0
  54. data/lib/puma/plugin.rb +120 -0
  55. data/lib/puma/plugin/tmp_restart.rb +36 -0
  56. data/lib/puma/rack/builder.rb +301 -0
  57. data/lib/puma/rack/urlmap.rb +93 -0
  58. data/lib/puma/rack_default.rb +9 -0
  59. data/lib/puma/reactor.rb +399 -0
  60. data/lib/puma/runner.rb +185 -0
  61. data/lib/puma/server.rb +1033 -0
  62. data/lib/puma/single.rb +124 -0
  63. data/lib/puma/state_file.rb +31 -0
  64. data/lib/puma/tcp_logger.rb +41 -0
  65. data/lib/puma/thread_pool.rb +328 -0
  66. data/lib/puma/util.rb +124 -0
  67. data/lib/rack/handler/puma.rb +115 -0
  68. data/tools/docker/Dockerfile +16 -0
  69. data/tools/jungle/README.md +19 -0
  70. data/tools/jungle/init.d/README.md +61 -0
  71. data/tools/jungle/init.d/puma +421 -0
  72. data/tools/jungle/init.d/run-puma +18 -0
  73. data/tools/jungle/rc.d/README.md +74 -0
  74. data/tools/jungle/rc.d/puma +61 -0
  75. data/tools/jungle/rc.d/puma.conf +10 -0
  76. data/tools/jungle/upstart/README.md +61 -0
  77. data/tools/jungle/upstart/puma-manager.conf +31 -0
  78. data/tools/jungle/upstart/puma.conf +69 -0
  79. data/tools/trickletest.rb +44 -0
  80. metadata +144 -0
@@ -0,0 +1,278 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require 'io/wait'
5
+ rescue LoadError
6
+ end
7
+
8
+ module Puma
9
+ module MiniSSL
10
+ class Socket
11
+ def initialize(socket, engine)
12
+ @socket = socket
13
+ @engine = engine
14
+ @peercert = nil
15
+ end
16
+
17
+ def to_io
18
+ @socket
19
+ end
20
+
21
+ def closed?
22
+ @socket.closed?
23
+ end
24
+
25
+ def readpartial(size)
26
+ while true
27
+ output = @engine.read
28
+ return output if output
29
+
30
+ data = @socket.readpartial(size)
31
+ @engine.inject(data)
32
+ output = @engine.read
33
+
34
+ return output if output
35
+
36
+ while neg_data = @engine.extract
37
+ @socket.write neg_data
38
+ end
39
+ end
40
+ end
41
+
42
+ def engine_read_all
43
+ output = @engine.read
44
+ while output and additional_output = @engine.read
45
+ output << additional_output
46
+ end
47
+ output
48
+ end
49
+
50
+ def read_nonblock(size, *_)
51
+ # *_ is to deal with keyword args that were added
52
+ # at some point (and being used in the wild)
53
+ while true
54
+ output = engine_read_all
55
+ return output if output
56
+
57
+ data = @socket.read_nonblock(size, exception: false)
58
+ if data == :wait_readable || data == :wait_writable
59
+ # It would make more sense to let @socket.read_nonblock raise
60
+ # EAGAIN if necessary but it seems like it'll misbehave on Windows.
61
+ # I don't have a Windows machine to debug this so I can't explain
62
+ # exactly whats happening in that OS. Please let me know if you
63
+ # find out!
64
+ #
65
+ # In the meantime, we can emulate the correct behavior by
66
+ # capturing :wait_readable & :wait_writable and raising EAGAIN
67
+ # ourselves.
68
+ raise IO::EAGAINWaitReadable
69
+ elsif data.nil?
70
+ return nil
71
+ end
72
+
73
+ @engine.inject(data)
74
+ output = engine_read_all
75
+
76
+ return output if output
77
+
78
+ while neg_data = @engine.extract
79
+ @socket.write neg_data
80
+ end
81
+ end
82
+ end
83
+
84
+ def write(data)
85
+ return 0 if data.empty?
86
+
87
+ need = data.bytesize
88
+
89
+ while true
90
+ wrote = @engine.write data
91
+ enc = @engine.extract
92
+
93
+ while enc
94
+ @socket.write enc
95
+ enc = @engine.extract
96
+ end
97
+
98
+ need -= wrote
99
+
100
+ return data.bytesize if need == 0
101
+
102
+ data = data[wrote..-1]
103
+ end
104
+ end
105
+
106
+ alias_method :syswrite, :write
107
+ alias_method :<<, :write
108
+
109
+ # This is a temporary fix to deal with websockets code using
110
+ # write_nonblock. The problem with implementing it properly
111
+ # is that it means we'd have to have the ability to rewind
112
+ # an engine because after we write+extract, the socket
113
+ # write_nonblock call might raise an exception and later
114
+ # code would pass the same data in, but the engine would think
115
+ # it had already written the data in. So for the time being
116
+ # (and since write blocking is quite rare), go ahead and actually
117
+ # block in write_nonblock.
118
+ def write_nonblock(data, *_)
119
+ write data
120
+ end
121
+
122
+ def flush
123
+ @socket.flush
124
+ end
125
+
126
+ def read_and_drop(timeout = 1)
127
+ return :timeout unless IO.select([@socket], nil, nil, timeout)
128
+ return :eof unless read_nonblock(1024)
129
+ :drop
130
+ rescue Errno::EAGAIN
131
+ # do nothing
132
+ :eagain
133
+ end
134
+
135
+ def should_drop_bytes?
136
+ @engine.init? || !@engine.shutdown
137
+ end
138
+
139
+ def close
140
+ begin
141
+ # Read any drop any partially initialized sockets and any received bytes during shutdown.
142
+ # Don't let this socket hold this loop forever.
143
+ # If it can't send more packets within 1s, then give up.
144
+ while should_drop_bytes?
145
+ return if [:timeout, :eof].include?(read_and_drop(1))
146
+ end
147
+ rescue IOError, SystemCallError
148
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
149
+ # nothing
150
+ ensure
151
+ @socket.close
152
+ end
153
+ end
154
+
155
+ def peeraddr
156
+ @socket.peeraddr
157
+ end
158
+
159
+ def peercert
160
+ return @peercert if @peercert
161
+
162
+ raw = @engine.peercert
163
+ return nil unless raw
164
+
165
+ @peercert = OpenSSL::X509::Certificate.new raw
166
+ end
167
+ end
168
+
169
+ if defined?(JRUBY_VERSION)
170
+ class SSLError < StandardError
171
+ # Define this for jruby even though it isn't used.
172
+ end
173
+
174
+ def self.check; end
175
+ end
176
+
177
+ class Context
178
+ attr_accessor :verify_mode
179
+ attr_reader :no_tlsv1, :no_tlsv1_1
180
+
181
+ def initialize
182
+ @no_tlsv1 = false
183
+ @no_tlsv1_1 = false
184
+ end
185
+
186
+ if defined?(JRUBY_VERSION)
187
+ # jruby-specific Context properties: java uses a keystore and password pair rather than a cert/key pair
188
+ attr_reader :keystore
189
+ attr_accessor :keystore_pass
190
+ attr_accessor :ssl_cipher_list
191
+
192
+ def keystore=(keystore)
193
+ raise ArgumentError, "No such keystore file '#{keystore}'" unless File.exist? keystore
194
+ @keystore = keystore
195
+ end
196
+
197
+ def check
198
+ raise "Keystore not configured" unless @keystore
199
+ end
200
+
201
+ else
202
+ # non-jruby Context properties
203
+ attr_reader :key
204
+ attr_reader :cert
205
+ attr_reader :ca
206
+ attr_accessor :ssl_cipher_filter
207
+
208
+ def key=(key)
209
+ raise ArgumentError, "No such key file '#{key}'" unless File.exist? key
210
+ @key = key
211
+ end
212
+
213
+ def cert=(cert)
214
+ raise ArgumentError, "No such cert file '#{cert}'" unless File.exist? cert
215
+ @cert = cert
216
+ end
217
+
218
+ def ca=(ca)
219
+ raise ArgumentError, "No such ca file '#{ca}'" unless File.exist? ca
220
+ @ca = ca
221
+ end
222
+
223
+ def check
224
+ raise "Key not configured" unless @key
225
+ raise "Cert not configured" unless @cert
226
+ end
227
+ end
228
+
229
+ # disables TLSv1
230
+ def no_tlsv1=(tlsv1)
231
+ raise ArgumentError, "Invalid value of no_tlsv1" unless ['true', 'false', true, false].include?(tlsv1)
232
+ @no_tlsv1 = tlsv1
233
+ end
234
+
235
+ # disables TLSv1 and TLSv1.1. Overrides `#no_tlsv1=`
236
+ def no_tlsv1_1=(tlsv1_1)
237
+ raise ArgumentError, "Invalid value of no_tlsv1" unless ['true', 'false', true, false].include?(tlsv1_1)
238
+ @no_tlsv1_1 = tlsv1_1
239
+ end
240
+
241
+ end
242
+
243
+ VERIFY_NONE = 0
244
+ VERIFY_PEER = 1
245
+ VERIFY_FAIL_IF_NO_PEER_CERT = 2
246
+
247
+ class Server
248
+ def initialize(socket, ctx)
249
+ @socket = socket
250
+ @ctx = ctx
251
+ end
252
+
253
+ def to_io
254
+ @socket
255
+ end
256
+
257
+ def accept
258
+ @ctx.check
259
+ io = @socket.accept
260
+ engine = Engine.server @ctx
261
+
262
+ Socket.new io, engine
263
+ end
264
+
265
+ def accept_nonblock
266
+ @ctx.check
267
+ io = @socket.accept_nonblock
268
+ engine = Engine.server @ctx
269
+
270
+ Socket.new io, engine
271
+ end
272
+
273
+ def close
274
+ @socket.close unless @socket.closed? # closed? call is for Windows
275
+ end
276
+ end
277
+ end
278
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Puma
4
+ # Provides an IO-like object that always appears to contain no data.
5
+ # Used as the value for rack.input when the request has no body.
6
+ #
7
+ class NullIO
8
+ def gets
9
+ nil
10
+ end
11
+
12
+ def each
13
+ end
14
+
15
+ # Mimics IO#read with no data.
16
+ #
17
+ def read(count = nil, _buffer = nil)
18
+ (count && count > 0) ? nil : ""
19
+ end
20
+
21
+ def rewind
22
+ end
23
+
24
+ def close
25
+ end
26
+
27
+ def size
28
+ 0
29
+ end
30
+
31
+ def eof?
32
+ true
33
+ end
34
+
35
+ def sync=(v)
36
+ end
37
+
38
+ def puts(*ary)
39
+ end
40
+
41
+ def write(*ary)
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Puma
4
+ class UnknownPlugin < RuntimeError; end
5
+
6
+ class PluginLoader
7
+ def initialize
8
+ @instances = []
9
+ end
10
+
11
+ def create(name)
12
+ if cls = Plugins.find(name)
13
+ plugin = cls.new(Plugin)
14
+ @instances << plugin
15
+ return plugin
16
+ end
17
+
18
+ raise UnknownPlugin, "File failed to register properly named plugin"
19
+ end
20
+
21
+ def fire_starts(launcher)
22
+ @instances.each do |i|
23
+ if i.respond_to? :start
24
+ i.start(launcher)
25
+ end
26
+ end
27
+ end
28
+ end
29
+
30
+ class PluginRegistry
31
+ def initialize
32
+ @plugins = {}
33
+ @background = []
34
+ end
35
+
36
+ def register(name, cls)
37
+ @plugins[name] = cls
38
+ end
39
+
40
+ def find(name)
41
+ name = name.to_s
42
+
43
+ if cls = @plugins[name]
44
+ return cls
45
+ end
46
+
47
+ begin
48
+ require "puma/plugin/#{name}"
49
+ rescue LoadError
50
+ raise UnknownPlugin, "Unable to find plugin: #{name}"
51
+ end
52
+
53
+ if cls = @plugins[name]
54
+ return cls
55
+ end
56
+
57
+ raise UnknownPlugin, "file failed to register a plugin"
58
+ end
59
+
60
+ def add_background(blk)
61
+ @background << blk
62
+ end
63
+
64
+ def fire_background
65
+ @background.each_with_index do |b, i|
66
+ Thread.new do
67
+ Puma.set_thread_name "plugin background #{i}"
68
+ b.call
69
+ end
70
+ end
71
+ end
72
+ end
73
+
74
+ Plugins = PluginRegistry.new
75
+
76
+ class Plugin
77
+ # Matches
78
+ # "C:/Ruby22/lib/ruby/gems/2.2.0/gems/puma-3.0.1/lib/puma/plugin/tmp_restart.rb:3:in `<top (required)>'"
79
+ # AS
80
+ # C:/Ruby22/lib/ruby/gems/2.2.0/gems/puma-3.0.1/lib/puma/plugin/tmp_restart.rb
81
+ CALLER_FILE = /
82
+ \A # start of string
83
+ .+ # file path (one or more characters)
84
+ (?= # stop previous match when
85
+ :\d+ # a colon is followed by one or more digits
86
+ :in # followed by a colon followed by in
87
+ )
88
+ /x
89
+
90
+ def self.extract_name(ary)
91
+ path = ary.first[CALLER_FILE]
92
+
93
+ m = %r!puma/plugin/([^/]*)\.rb$!.match(path)
94
+ return m[1]
95
+ end
96
+
97
+ def self.create(&blk)
98
+ name = extract_name(caller)
99
+
100
+ cls = Class.new(self)
101
+
102
+ cls.class_eval(&blk)
103
+
104
+ Plugins.register name, cls
105
+ end
106
+
107
+ def initialize(loader)
108
+ @loader = loader
109
+ end
110
+
111
+ def in_background(&blk)
112
+ Plugins.add_background blk
113
+ end
114
+
115
+ def workers_supported?
116
+ return false if Puma.jruby? || Puma.windows?
117
+ true
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'puma/plugin'
4
+
5
+ Puma::Plugin.create do
6
+ def start(launcher)
7
+ path = File.join("tmp", "restart.txt")
8
+
9
+ orig = nil
10
+
11
+ # If we can't write to the path, then just don't bother with this plugin
12
+ begin
13
+ File.write(path, "") unless File.exist?(path)
14
+ orig = File.stat(path).mtime
15
+ rescue SystemCallError
16
+ return
17
+ end
18
+
19
+ in_background do
20
+ while true
21
+ sleep 2
22
+
23
+ begin
24
+ mtime = File.stat(path).mtime
25
+ rescue SystemCallError
26
+ # If the file has disappeared, assume that means don't restart
27
+ else
28
+ if mtime > orig
29
+ launcher.restart
30
+ break
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end