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,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Standard libraries
4
+ require 'socket'
5
+ require 'tempfile'
6
+ require 'time'
7
+ require 'etc'
8
+ require 'uri'
9
+ require 'stringio'
10
+
11
+ require 'thread'
12
+
13
+ module Puma
14
+ autoload :Const, 'puma/const'
15
+ autoload :Server, 'puma/server'
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
31
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'openssl'
4
+
5
+ module OpenSSL
6
+ module SSL
7
+ class SSLServer
8
+ unless public_method_defined? :accept_nonblock
9
+ def accept_nonblock
10
+ sock = @svr.accept_nonblock
11
+
12
+ begin
13
+ ssl = OpenSSL::SSL::SSLSocket.new(sock, @ctx)
14
+ ssl.sync_close = true
15
+ ssl.accept if @start_immediately
16
+ ssl
17
+ rescue SSLError => ex
18
+ if ssl
19
+ ssl.close
20
+ else
21
+ sock.close
22
+ end
23
+ raise ex
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module Puma
6
+ module App
7
+ # Check out {#call}'s source code to see what actions this web application
8
+ # can respond to.
9
+ class Status
10
+ OK_STATUS = '{ "status": "ok" }'.freeze
11
+
12
+ def initialize(cli, token = nil)
13
+ @cli = cli
14
+ @auth_token = token
15
+ end
16
+
17
+ def call(env)
18
+ unless authenticate(env)
19
+ return rack_response(403, 'Invalid auth token', 'text/plain')
20
+ end
21
+
22
+ case env['PATH_INFO']
23
+ when /\/stop$/
24
+ @cli.stop
25
+ rack_response(200, OK_STATUS)
26
+
27
+ when /\/halt$/
28
+ @cli.halt
29
+ rack_response(200, OK_STATUS)
30
+
31
+ when /\/restart$/
32
+ @cli.restart
33
+ rack_response(200, OK_STATUS)
34
+
35
+ when /\/phased-restart$/
36
+ if !@cli.phased_restart
37
+ rack_response(404, '{ "error": "phased restart not available" }')
38
+ else
39
+ rack_response(200, OK_STATUS)
40
+ end
41
+
42
+ when /\/reload-worker-directory$/
43
+ if !@cli.send(:reload_worker_directory)
44
+ rack_response(404, '{ "error": "reload_worker_directory not available" }')
45
+ else
46
+ rack_response(200, OK_STATUS)
47
+ end
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
+
56
+ when /\/stats$/
57
+ rack_response(200, @cli.stats)
58
+ else
59
+ rack_response 404, "Unsupported action", 'text/plain'
60
+ end
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
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,439 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'uri'
4
+ require 'socket'
5
+
6
+ require 'puma/const'
7
+ require 'puma/util'
8
+
9
+ module Puma
10
+ class Binder
11
+ include Puma::Const
12
+
13
+ RACK_VERSION = [1,3].freeze
14
+
15
+ def initialize(events)
16
+ @events = events
17
+ @listeners = []
18
+ @inherited_fds = {}
19
+ @activated_sockets = {}
20
+ @unix_paths = []
21
+
22
+ @proto_env = {
23
+ "rack.version".freeze => RACK_VERSION,
24
+ "rack.errors".freeze => events.stderr,
25
+ "rack.multithread".freeze => true,
26
+ "rack.multiprocess".freeze => false,
27
+ "rack.run_once".freeze => false,
28
+ "SCRIPT_NAME".freeze => ENV['SCRIPT_NAME'] || "",
29
+
30
+ # I'd like to set a default CONTENT_TYPE here but some things
31
+ # depend on their not being a default set and inferring
32
+ # it from the content. And so if i set it here, it won't
33
+ # infer properly.
34
+
35
+ "QUERY_STRING".freeze => "",
36
+ SERVER_PROTOCOL => HTTP_11,
37
+ SERVER_SOFTWARE => PUMA_SERVER_STRING,
38
+ GATEWAY_INTERFACE => CGI_VER
39
+ }
40
+
41
+ @envs = {}
42
+ @ios = []
43
+ end
44
+
45
+ attr_reader :ios
46
+
47
+ def env(sock)
48
+ @envs.fetch(sock, @proto_env)
49
+ end
50
+
51
+ def close
52
+ @ios.each { |i| i.close }
53
+ end
54
+
55
+ def import_from_env
56
+ remove = []
57
+
58
+ ENV.each do |k,v|
59
+ if k =~ /PUMA_INHERIT_\d+/
60
+ fd, url = v.split(":", 2)
61
+ @inherited_fds[url] = fd.to_i
62
+ remove << k
63
+ elsif k == 'LISTEN_FDS' && ENV['LISTEN_PID'].to_i == $$
64
+ v.to_i.times do |num|
65
+ fd = num + 3
66
+ sock = TCPServer.for_fd(fd)
67
+ begin
68
+ key = [ :unix, Socket.unpack_sockaddr_un(sock.getsockname) ]
69
+ rescue ArgumentError
70
+ port, addr = Socket.unpack_sockaddr_in(sock.getsockname)
71
+ if addr =~ /\:/
72
+ addr = "[#{addr}]"
73
+ end
74
+ key = [ :tcp, addr, port ]
75
+ end
76
+ @activated_sockets[key] = sock
77
+ @events.debug "Registered #{key.join ':'} for activation from LISTEN_FDS"
78
+ end
79
+ remove << k << 'LISTEN_PID'
80
+ end
81
+ end
82
+
83
+ remove.each do |k|
84
+ ENV.delete k
85
+ end
86
+ end
87
+
88
+ def parse(binds, logger)
89
+ binds.each do |str|
90
+ uri = URI.parse str
91
+ case uri.scheme
92
+ when "tcp"
93
+ if fd = @inherited_fds.delete(str)
94
+ io = inherit_tcp_listener uri.host, uri.port, fd
95
+ logger.log "* Inherited #{str}"
96
+ elsif sock = @activated_sockets.delete([ :tcp, uri.host, uri.port ])
97
+ io = inherit_tcp_listener uri.host, uri.port, sock
98
+ logger.log "* Activated #{str}"
99
+ else
100
+ params = Util.parse_query uri.query
101
+
102
+ opt = params.key?('low_latency')
103
+ bak = params.fetch('backlog', 1024).to_i
104
+
105
+ io = add_tcp_listener uri.host, uri.port, opt, bak
106
+
107
+ @ios.each do |i|
108
+ addr = if i.local_address.ipv6?
109
+ "[#{i.local_address.ip_unpack[0]}]:#{i.local_address.ip_unpack[1]}"
110
+ else
111
+ i.local_address.ip_unpack.join(':')
112
+ end
113
+
114
+ logger.log "* Listening on tcp://#{addr}"
115
+ end
116
+ end
117
+
118
+ @listeners << [str, io] if io
119
+ when "unix"
120
+ path = "#{uri.host}#{uri.path}".gsub("%20", " ")
121
+
122
+ if fd = @inherited_fds.delete(str)
123
+ io = inherit_unix_listener path, fd
124
+ logger.log "* Inherited #{str}"
125
+ elsif sock = @activated_sockets.delete([ :unix, path ])
126
+ io = inherit_unix_listener path, sock
127
+ logger.log "* Activated #{str}"
128
+ else
129
+ umask = nil
130
+ mode = nil
131
+ backlog = 1024
132
+
133
+ if uri.query
134
+ params = Util.parse_query uri.query
135
+ if u = params['umask']
136
+ # Use Integer() to respect the 0 prefix as octal
137
+ umask = Integer(u)
138
+ end
139
+
140
+ if u = params['mode']
141
+ mode = Integer('0'+u)
142
+ end
143
+
144
+ if u = params['backlog']
145
+ backlog = Integer(u)
146
+ end
147
+ end
148
+
149
+ io = add_unix_listener path, umask, mode, backlog
150
+ logger.log "* Listening on #{str}"
151
+ end
152
+
153
+ @listeners << [str, io]
154
+ when "ssl"
155
+ params = Util.parse_query uri.query
156
+ require 'puma/minissl'
157
+
158
+ MiniSSL.check
159
+
160
+ ctx = MiniSSL::Context.new
161
+
162
+ if defined?(JRUBY_VERSION)
163
+ unless params['keystore']
164
+ @events.error "Please specify the Java keystore via 'keystore='"
165
+ end
166
+
167
+ ctx.keystore = params['keystore']
168
+
169
+ unless params['keystore-pass']
170
+ @events.error "Please specify the Java keystore password via 'keystore-pass='"
171
+ end
172
+
173
+ ctx.keystore_pass = params['keystore-pass']
174
+ ctx.ssl_cipher_list = params['ssl_cipher_list'] if params['ssl_cipher_list']
175
+ else
176
+ unless params['key']
177
+ @events.error "Please specify the SSL key via 'key='"
178
+ end
179
+
180
+ ctx.key = params['key']
181
+
182
+ unless params['cert']
183
+ @events.error "Please specify the SSL cert via 'cert='"
184
+ end
185
+
186
+ ctx.cert = params['cert']
187
+
188
+ if ['peer', 'force_peer'].include?(params['verify_mode'])
189
+ unless params['ca']
190
+ @events.error "Please specify the SSL ca via 'ca='"
191
+ end
192
+ end
193
+
194
+ ctx.ca = params['ca'] if params['ca']
195
+ ctx.ssl_cipher_filter = params['ssl_cipher_filter'] if params['ssl_cipher_filter']
196
+ end
197
+
198
+ ctx.no_tlsv1 = true if params['no_tlsv1'] == 'true'
199
+ ctx.no_tlsv1_1 = true if params['no_tlsv1_1'] == 'true'
200
+
201
+ if params['verify_mode']
202
+ ctx.verify_mode = case params['verify_mode']
203
+ when "peer"
204
+ MiniSSL::VERIFY_PEER
205
+ when "force_peer"
206
+ MiniSSL::VERIFY_PEER | MiniSSL::VERIFY_FAIL_IF_NO_PEER_CERT
207
+ when "none"
208
+ MiniSSL::VERIFY_NONE
209
+ else
210
+ @events.error "Please specify a valid verify_mode="
211
+ MiniSSL::VERIFY_NONE
212
+ end
213
+ end
214
+
215
+ if fd = @inherited_fds.delete(str)
216
+ logger.log "* Inherited #{str}"
217
+ io = inherit_ssl_listener fd, ctx
218
+ elsif sock = @activated_sockets.delete([ :tcp, uri.host, uri.port ])
219
+ io = inherit_ssl_listener sock, ctx
220
+ logger.log "* Activated #{str}"
221
+ else
222
+ io = add_ssl_listener uri.host, uri.port, ctx
223
+ logger.log "* Listening on #{str}"
224
+ end
225
+
226
+ @listeners << [str, io] if io
227
+ else
228
+ logger.error "Invalid URI: #{str}"
229
+ end
230
+ end
231
+
232
+ # If we inherited fds but didn't use them (because of a
233
+ # configuration change), then be sure to close them.
234
+ @inherited_fds.each do |str, fd|
235
+ logger.log "* Closing unused inherited connection: #{str}"
236
+
237
+ begin
238
+ IO.for_fd(fd).close
239
+ rescue SystemCallError
240
+ end
241
+
242
+ # We have to unlink a unix socket path that's not being used
243
+ uri = URI.parse str
244
+ if uri.scheme == "unix"
245
+ path = "#{uri.host}#{uri.path}"
246
+ File.unlink path
247
+ end
248
+ end
249
+
250
+ # Also close any unused activated sockets
251
+ @activated_sockets.each do |key, sock|
252
+ logger.log "* Closing unused activated socket: #{key.join ':'}"
253
+ begin
254
+ sock.close
255
+ rescue SystemCallError
256
+ end
257
+ # We have to unlink a unix socket path that's not being used
258
+ File.unlink key[1] if key[0] == :unix
259
+ end
260
+ end
261
+
262
+ def loopback_addresses
263
+ Socket.ip_address_list.select do |addrinfo|
264
+ addrinfo.ipv6_loopback? || addrinfo.ipv4_loopback?
265
+ end.map { |addrinfo| addrinfo.ip_address }.uniq
266
+ end
267
+
268
+ # Tell the server to listen on host +host+, port +port+.
269
+ # If +optimize_for_latency+ is true (the default) then clients connecting
270
+ # will be optimized for latency over throughput.
271
+ #
272
+ # +backlog+ indicates how many unaccepted connections the kernel should
273
+ # allow to accumulate before returning connection refused.
274
+ #
275
+ def add_tcp_listener(host, port, optimize_for_latency=true, backlog=1024)
276
+ if host == "localhost"
277
+ loopback_addresses.each do |addr|
278
+ add_tcp_listener addr, port, optimize_for_latency, backlog
279
+ end
280
+ return
281
+ end
282
+
283
+ host = host[1..-2] if host and host[0..0] == '['
284
+ s = TCPServer.new(host, port)
285
+ if optimize_for_latency
286
+ s.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
287
+ end
288
+ s.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
289
+ s.listen backlog
290
+ @connected_port = s.addr[1]
291
+
292
+ @ios << s
293
+ s
294
+ end
295
+
296
+ attr_reader :connected_port
297
+
298
+ def inherit_tcp_listener(host, port, fd)
299
+ if fd.kind_of? TCPServer
300
+ s = fd
301
+ else
302
+ s = TCPServer.for_fd(fd)
303
+ end
304
+
305
+ @ios << s
306
+ s
307
+ end
308
+
309
+ def add_ssl_listener(host, port, ctx,
310
+ optimize_for_latency=true, backlog=1024)
311
+ require 'puma/minissl'
312
+
313
+ MiniSSL.check
314
+
315
+ if host == "localhost"
316
+ loopback_addresses.each do |addr|
317
+ add_ssl_listener addr, port, ctx, optimize_for_latency, backlog
318
+ end
319
+ return
320
+ end
321
+
322
+ host = host[1..-2] if host[0..0] == '['
323
+ s = TCPServer.new(host, port)
324
+ if optimize_for_latency
325
+ s.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
326
+ end
327
+ s.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
328
+ s.listen backlog
329
+
330
+
331
+ ssl = MiniSSL::Server.new s, ctx
332
+ env = @proto_env.dup
333
+ env[HTTPS_KEY] = HTTPS
334
+ @envs[ssl] = env
335
+
336
+ @ios << ssl
337
+ s
338
+ end
339
+
340
+ def inherit_ssl_listener(fd, ctx)
341
+ require 'puma/minissl'
342
+ MiniSSL.check
343
+
344
+ if fd.kind_of? TCPServer
345
+ s = fd
346
+ else
347
+ s = TCPServer.for_fd(fd)
348
+ end
349
+ ssl = MiniSSL::Server.new(s, ctx)
350
+
351
+ env = @proto_env.dup
352
+ env[HTTPS_KEY] = HTTPS
353
+ @envs[ssl] = env
354
+
355
+ @ios << ssl
356
+
357
+ s
358
+ end
359
+
360
+ # Tell the server to listen on +path+ as a UNIX domain socket.
361
+ #
362
+ def add_unix_listener(path, umask=nil, mode=nil, backlog=1024)
363
+ @unix_paths << path
364
+
365
+ # Let anyone connect by default
366
+ umask ||= 0
367
+
368
+ begin
369
+ old_mask = File.umask(umask)
370
+
371
+ if File.exist? path
372
+ begin
373
+ old = UNIXSocket.new path
374
+ rescue SystemCallError, IOError
375
+ File.unlink path
376
+ else
377
+ old.close
378
+ raise "There is already a server bound to: #{path}"
379
+ end
380
+ end
381
+
382
+ s = UNIXServer.new(path)
383
+ s.listen backlog
384
+ @ios << s
385
+ ensure
386
+ File.umask old_mask
387
+ end
388
+
389
+ if mode
390
+ File.chmod mode, path
391
+ end
392
+
393
+ env = @proto_env.dup
394
+ env[REMOTE_ADDR] = "127.0.0.1"
395
+ @envs[s] = env
396
+
397
+ s
398
+ end
399
+
400
+ def inherit_unix_listener(path, fd)
401
+ @unix_paths << path
402
+
403
+ if fd.kind_of? TCPServer
404
+ s = fd
405
+ else
406
+ s = UNIXServer.for_fd fd
407
+ end
408
+ @ios << s
409
+
410
+ env = @proto_env.dup
411
+ env[REMOTE_ADDR] = "127.0.0.1"
412
+ @envs[s] = env
413
+
414
+ s
415
+ end
416
+
417
+ def close_listeners
418
+ @listeners.each do |l, io|
419
+ io.close
420
+ uri = URI.parse(l)
421
+ next unless uri.scheme == 'unix'
422
+ File.unlink("#{uri.host}#{uri.path}")
423
+ end
424
+ end
425
+
426
+ def close_unix_paths
427
+ @unix_paths.each { |up| File.unlink(up) if File.exist? up }
428
+ end
429
+
430
+ def redirects_for_restart
431
+ redirects = {:close_others => true}
432
+ @listeners.each_with_index do |(l, io), i|
433
+ ENV["PUMA_INHERIT_#{i}"] = "#{io.to_i}:#{l}"
434
+ redirects[io.to_i] = io.to_i
435
+ end
436
+ redirects
437
+ end
438
+ end
439
+ end