puma 4.3.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 +1532 -0
  3. data/LICENSE +26 -0
  4. data/README.md +291 -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 +38 -0
  15. data/docs/restart.md +41 -0
  16. data/docs/signals.md +96 -0
  17. data/docs/systemd.md +290 -0
  18. data/docs/tcp_mode.md +96 -0
  19. data/ext/puma_http11/PumaHttp11Service.java +19 -0
  20. data/ext/puma_http11/ext_help.h +15 -0
  21. data/ext/puma_http11/extconf.rb +28 -0
  22. data/ext/puma_http11/http11_parser.c +1044 -0
  23. data/ext/puma_http11/http11_parser.h +65 -0
  24. data/ext/puma_http11/http11_parser.java.rl +145 -0
  25. data/ext/puma_http11/http11_parser.rl +147 -0
  26. data/ext/puma_http11/http11_parser_common.rl +54 -0
  27. data/ext/puma_http11/io_buffer.c +155 -0
  28. data/ext/puma_http11/mini_ssl.c +553 -0
  29. data/ext/puma_http11/org/jruby/puma/Http11.java +226 -0
  30. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +455 -0
  31. data/ext/puma_http11/org/jruby/puma/IOBuffer.java +72 -0
  32. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +363 -0
  33. data/ext/puma_http11/puma_http11.c +502 -0
  34. data/lib/puma.rb +31 -0
  35. data/lib/puma/accept_nonblock.rb +29 -0
  36. data/lib/puma/app/status.rb +80 -0
  37. data/lib/puma/binder.rb +385 -0
  38. data/lib/puma/cli.rb +239 -0
  39. data/lib/puma/client.rb +494 -0
  40. data/lib/puma/cluster.rb +554 -0
  41. data/lib/puma/commonlogger.rb +108 -0
  42. data/lib/puma/configuration.rb +362 -0
  43. data/lib/puma/const.rb +235 -0
  44. data/lib/puma/control_cli.rb +289 -0
  45. data/lib/puma/detect.rb +15 -0
  46. data/lib/puma/dsl.rb +740 -0
  47. data/lib/puma/events.rb +156 -0
  48. data/lib/puma/io_buffer.rb +4 -0
  49. data/lib/puma/jruby_restart.rb +84 -0
  50. data/lib/puma/launcher.rb +475 -0
  51. data/lib/puma/minissl.rb +278 -0
  52. data/lib/puma/minissl/context_builder.rb +76 -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 +400 -0
  60. data/lib/puma/runner.rb +192 -0
  61. data/lib/puma/server.rb +1030 -0
  62. data/lib/puma/single.rb +123 -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,385 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'uri'
4
+ require 'socket'
5
+
6
+ require 'puma/const'
7
+ require 'puma/util'
8
+ require 'puma/minissl/context_builder'
9
+
10
+ module Puma
11
+ class Binder
12
+ include Puma::Const
13
+
14
+ RACK_VERSION = [1,3].freeze
15
+
16
+ def initialize(events)
17
+ @events = events
18
+ @listeners = []
19
+ @inherited_fds = {}
20
+ @activated_sockets = {}
21
+ @unix_paths = []
22
+
23
+ @proto_env = {
24
+ "rack.version".freeze => RACK_VERSION,
25
+ "rack.errors".freeze => events.stderr,
26
+ "rack.multithread".freeze => true,
27
+ "rack.multiprocess".freeze => false,
28
+ "rack.run_once".freeze => false,
29
+ "SCRIPT_NAME".freeze => ENV['SCRIPT_NAME'] || "",
30
+
31
+ # I'd like to set a default CONTENT_TYPE here but some things
32
+ # depend on their not being a default set and inferring
33
+ # it from the content. And so if i set it here, it won't
34
+ # infer properly.
35
+
36
+ "QUERY_STRING".freeze => "",
37
+ SERVER_PROTOCOL => HTTP_11,
38
+ SERVER_SOFTWARE => PUMA_SERVER_STRING,
39
+ GATEWAY_INTERFACE => CGI_VER
40
+ }
41
+
42
+ @envs = {}
43
+ @ios = []
44
+ end
45
+
46
+ attr_reader :ios
47
+
48
+ def env(sock)
49
+ @envs.fetch(sock, @proto_env)
50
+ end
51
+
52
+ def close
53
+ @ios.each { |i| i.close }
54
+ end
55
+
56
+ def import_from_env
57
+ remove = []
58
+
59
+ ENV.each do |k,v|
60
+ if k =~ /PUMA_INHERIT_\d+/
61
+ fd, url = v.split(":", 2)
62
+ @inherited_fds[url] = fd.to_i
63
+ remove << k
64
+ elsif k == 'LISTEN_FDS' && ENV['LISTEN_PID'].to_i == $$
65
+ v.to_i.times do |num|
66
+ fd = num + 3
67
+ sock = TCPServer.for_fd(fd)
68
+ begin
69
+ key = [ :unix, Socket.unpack_sockaddr_un(sock.getsockname) ]
70
+ rescue ArgumentError
71
+ port, addr = Socket.unpack_sockaddr_in(sock.getsockname)
72
+ if addr =~ /\:/
73
+ addr = "[#{addr}]"
74
+ end
75
+ key = [ :tcp, addr, port ]
76
+ end
77
+ @activated_sockets[key] = sock
78
+ @events.debug "Registered #{key.join ':'} for activation from LISTEN_FDS"
79
+ end
80
+ remove << k << 'LISTEN_PID'
81
+ end
82
+ end
83
+
84
+ remove.each do |k|
85
+ ENV.delete k
86
+ end
87
+ end
88
+
89
+ def parse(binds, logger)
90
+ binds.each do |str|
91
+ uri = URI.parse str
92
+ case uri.scheme
93
+ when "tcp"
94
+ if fd = @inherited_fds.delete(str)
95
+ io = inherit_tcp_listener uri.host, uri.port, fd
96
+ logger.log "* Inherited #{str}"
97
+ elsif sock = @activated_sockets.delete([ :tcp, uri.host, uri.port ])
98
+ io = inherit_tcp_listener uri.host, uri.port, sock
99
+ logger.log "* Activated #{str}"
100
+ else
101
+ params = Util.parse_query uri.query
102
+
103
+ opt = params.key?('low_latency')
104
+ bak = params.fetch('backlog', 1024).to_i
105
+
106
+ io = add_tcp_listener uri.host, uri.port, opt, bak
107
+
108
+ @ios.each do |i|
109
+ next unless TCPServer === i
110
+ addr = if i.local_address.ipv6?
111
+ "[#{i.local_address.ip_unpack[0]}]:#{i.local_address.ip_unpack[1]}"
112
+ else
113
+ i.local_address.ip_unpack.join(':')
114
+ end
115
+
116
+ logger.log "* Listening on tcp://#{addr}"
117
+ end
118
+ end
119
+
120
+ @listeners << [str, io] if io
121
+ when "unix"
122
+ path = "#{uri.host}#{uri.path}".gsub("%20", " ")
123
+
124
+ if fd = @inherited_fds.delete(str)
125
+ io = inherit_unix_listener path, fd
126
+ logger.log "* Inherited #{str}"
127
+ elsif sock = @activated_sockets.delete([ :unix, path ])
128
+ io = inherit_unix_listener path, sock
129
+ logger.log "* Activated #{str}"
130
+ else
131
+ umask = nil
132
+ mode = nil
133
+ backlog = 1024
134
+
135
+ if uri.query
136
+ params = Util.parse_query uri.query
137
+ if u = params['umask']
138
+ # Use Integer() to respect the 0 prefix as octal
139
+ umask = Integer(u)
140
+ end
141
+
142
+ if u = params['mode']
143
+ mode = Integer('0'+u)
144
+ end
145
+
146
+ if u = params['backlog']
147
+ backlog = Integer(u)
148
+ end
149
+ end
150
+
151
+ io = add_unix_listener path, umask, mode, backlog
152
+ logger.log "* Listening on #{str}"
153
+ end
154
+
155
+ @listeners << [str, io]
156
+ when "ssl"
157
+ params = Util.parse_query uri.query
158
+ ctx = MiniSSL::ContextBuilder.new(params, @events).context
159
+
160
+ if fd = @inherited_fds.delete(str)
161
+ logger.log "* Inherited #{str}"
162
+ io = inherit_ssl_listener fd, ctx
163
+ elsif sock = @activated_sockets.delete([ :tcp, uri.host, uri.port ])
164
+ io = inherit_ssl_listener sock, ctx
165
+ logger.log "* Activated #{str}"
166
+ else
167
+ io = add_ssl_listener uri.host, uri.port, ctx
168
+ logger.log "* Listening on #{str}"
169
+ end
170
+
171
+ @listeners << [str, io] if io
172
+ else
173
+ logger.error "Invalid URI: #{str}"
174
+ end
175
+ end
176
+
177
+ # If we inherited fds but didn't use them (because of a
178
+ # configuration change), then be sure to close them.
179
+ @inherited_fds.each do |str, fd|
180
+ logger.log "* Closing unused inherited connection: #{str}"
181
+
182
+ begin
183
+ IO.for_fd(fd).close
184
+ rescue SystemCallError
185
+ end
186
+
187
+ # We have to unlink a unix socket path that's not being used
188
+ uri = URI.parse str
189
+ if uri.scheme == "unix"
190
+ path = "#{uri.host}#{uri.path}"
191
+ File.unlink path
192
+ end
193
+ end
194
+
195
+ # Also close any unused activated sockets
196
+ @activated_sockets.each do |key, sock|
197
+ logger.log "* Closing unused activated socket: #{key.join ':'}"
198
+ begin
199
+ sock.close
200
+ rescue SystemCallError
201
+ end
202
+ # We have to unlink a unix socket path that's not being used
203
+ File.unlink key[1] if key[0] == :unix
204
+ end
205
+ end
206
+
207
+ def loopback_addresses
208
+ Socket.ip_address_list.select do |addrinfo|
209
+ addrinfo.ipv6_loopback? || addrinfo.ipv4_loopback?
210
+ end.map { |addrinfo| addrinfo.ip_address }.uniq
211
+ end
212
+
213
+ # Tell the server to listen on host +host+, port +port+.
214
+ # If +optimize_for_latency+ is true (the default) then clients connecting
215
+ # will be optimized for latency over throughput.
216
+ #
217
+ # +backlog+ indicates how many unaccepted connections the kernel should
218
+ # allow to accumulate before returning connection refused.
219
+ #
220
+ def add_tcp_listener(host, port, optimize_for_latency=true, backlog=1024)
221
+ if host == "localhost"
222
+ loopback_addresses.each do |addr|
223
+ add_tcp_listener addr, port, optimize_for_latency, backlog
224
+ end
225
+ return
226
+ end
227
+
228
+ host = host[1..-2] if host and host[0..0] == '['
229
+ s = TCPServer.new(host, port)
230
+ if optimize_for_latency
231
+ s.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
232
+ end
233
+ s.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
234
+ s.listen backlog
235
+ @connected_port = s.addr[1]
236
+
237
+ @ios << s
238
+ s
239
+ end
240
+
241
+ attr_reader :connected_port
242
+
243
+ def inherit_tcp_listener(host, port, fd)
244
+ if fd.kind_of? TCPServer
245
+ s = fd
246
+ else
247
+ s = TCPServer.for_fd(fd)
248
+ end
249
+
250
+ @ios << s
251
+ s
252
+ end
253
+
254
+ def add_ssl_listener(host, port, ctx,
255
+ optimize_for_latency=true, backlog=1024)
256
+ require 'puma/minissl'
257
+
258
+ MiniSSL.check
259
+
260
+ if host == "localhost"
261
+ loopback_addresses.each do |addr|
262
+ add_ssl_listener addr, port, ctx, optimize_for_latency, backlog
263
+ end
264
+ return
265
+ end
266
+
267
+ host = host[1..-2] if host[0..0] == '['
268
+ s = TCPServer.new(host, port)
269
+ if optimize_for_latency
270
+ s.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
271
+ end
272
+ s.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
273
+ s.listen backlog
274
+
275
+
276
+ ssl = MiniSSL::Server.new s, ctx
277
+ env = @proto_env.dup
278
+ env[HTTPS_KEY] = HTTPS
279
+ @envs[ssl] = env
280
+
281
+ @ios << ssl
282
+ s
283
+ end
284
+
285
+ def inherit_ssl_listener(fd, ctx)
286
+ require 'puma/minissl'
287
+ MiniSSL.check
288
+
289
+ if fd.kind_of? TCPServer
290
+ s = fd
291
+ else
292
+ s = TCPServer.for_fd(fd)
293
+ end
294
+ ssl = MiniSSL::Server.new(s, ctx)
295
+
296
+ env = @proto_env.dup
297
+ env[HTTPS_KEY] = HTTPS
298
+ @envs[ssl] = env
299
+
300
+ @ios << ssl
301
+
302
+ s
303
+ end
304
+
305
+ # Tell the server to listen on +path+ as a UNIX domain socket.
306
+ #
307
+ def add_unix_listener(path, umask=nil, mode=nil, backlog=1024)
308
+ @unix_paths << path unless File.exist? path
309
+
310
+ # Let anyone connect by default
311
+ umask ||= 0
312
+
313
+ begin
314
+ old_mask = File.umask(umask)
315
+
316
+ if File.exist? path
317
+ begin
318
+ old = UNIXSocket.new path
319
+ rescue SystemCallError, IOError
320
+ File.unlink path
321
+ else
322
+ old.close
323
+ raise "There is already a server bound to: #{path}"
324
+ end
325
+ end
326
+
327
+ s = UNIXServer.new(path)
328
+ s.listen backlog
329
+ @ios << s
330
+ ensure
331
+ File.umask old_mask
332
+ end
333
+
334
+ if mode
335
+ File.chmod mode, path
336
+ end
337
+
338
+ env = @proto_env.dup
339
+ env[REMOTE_ADDR] = "127.0.0.1"
340
+ @envs[s] = env
341
+
342
+ s
343
+ end
344
+
345
+ def inherit_unix_listener(path, fd)
346
+ @unix_paths << path unless File.exist? path
347
+
348
+ if fd.kind_of? TCPServer
349
+ s = fd
350
+ else
351
+ s = UNIXServer.for_fd fd
352
+ end
353
+ @ios << s
354
+
355
+ env = @proto_env.dup
356
+ env[REMOTE_ADDR] = "127.0.0.1"
357
+ @envs[s] = env
358
+
359
+ s
360
+ end
361
+
362
+ def close_listeners
363
+ @listeners.each do |l, io|
364
+ io.close
365
+ uri = URI.parse(l)
366
+ next unless uri.scheme == 'unix'
367
+ unix_path = "#{uri.host}#{uri.path}"
368
+ File.unlink unix_path if @unix_paths.include? unix_path
369
+ end
370
+ end
371
+
372
+ def close_unix_paths
373
+ @unix_paths.each { |up| File.unlink(up) if File.exist? up }
374
+ end
375
+
376
+ def redirects_for_restart
377
+ redirects = {:close_others => true}
378
+ @listeners.each_with_index do |(l, io), i|
379
+ ENV["PUMA_INHERIT_#{i}"] = "#{io.to_i}:#{l}"
380
+ redirects[io.to_i] = io.to_i
381
+ end
382
+ redirects
383
+ end
384
+ end
385
+ end