wendell-puma 2.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. checksums.yaml +7 -0
  2. data/COPYING +55 -0
  3. data/DEPLOYMENT.md +92 -0
  4. data/Gemfile +17 -0
  5. data/History.txt +588 -0
  6. data/LICENSE +26 -0
  7. data/Manifest.txt +68 -0
  8. data/README.md +251 -0
  9. data/Rakefile +158 -0
  10. data/bin/puma +10 -0
  11. data/bin/puma-wild +31 -0
  12. data/bin/pumactl +12 -0
  13. data/docs/config.md +0 -0
  14. data/docs/nginx.md +80 -0
  15. data/docs/signals.md +43 -0
  16. data/ext/puma_http11/PumaHttp11Service.java +17 -0
  17. data/ext/puma_http11/ext_help.h +15 -0
  18. data/ext/puma_http11/extconf.rb +9 -0
  19. data/ext/puma_http11/http11_parser.c +1225 -0
  20. data/ext/puma_http11/http11_parser.h +64 -0
  21. data/ext/puma_http11/http11_parser.java.rl +161 -0
  22. data/ext/puma_http11/http11_parser.rl +146 -0
  23. data/ext/puma_http11/http11_parser_common.rl +54 -0
  24. data/ext/puma_http11/io_buffer.c +155 -0
  25. data/ext/puma_http11/mini_ssl.c +198 -0
  26. data/ext/puma_http11/org/jruby/puma/Http11.java +225 -0
  27. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +488 -0
  28. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +391 -0
  29. data/ext/puma_http11/puma_http11.c +491 -0
  30. data/lib/puma.rb +14 -0
  31. data/lib/puma/accept_nonblock.rb +23 -0
  32. data/lib/puma/app/status.rb +59 -0
  33. data/lib/puma/binder.rb +298 -0
  34. data/lib/puma/capistrano.rb +86 -0
  35. data/lib/puma/cli.rb +606 -0
  36. data/lib/puma/client.rb +289 -0
  37. data/lib/puma/cluster.rb +404 -0
  38. data/lib/puma/compat.rb +18 -0
  39. data/lib/puma/configuration.rb +377 -0
  40. data/lib/puma/const.rb +165 -0
  41. data/lib/puma/control_cli.rb +251 -0
  42. data/lib/puma/daemon_ext.rb +25 -0
  43. data/lib/puma/delegation.rb +11 -0
  44. data/lib/puma/detect.rb +4 -0
  45. data/lib/puma/events.rb +130 -0
  46. data/lib/puma/io_buffer.rb +7 -0
  47. data/lib/puma/java_io_buffer.rb +45 -0
  48. data/lib/puma/jruby_restart.rb +83 -0
  49. data/lib/puma/minissl.rb +187 -0
  50. data/lib/puma/null_io.rb +34 -0
  51. data/lib/puma/rack_default.rb +7 -0
  52. data/lib/puma/rack_patch.rb +45 -0
  53. data/lib/puma/reactor.rb +183 -0
  54. data/lib/puma/runner.rb +146 -0
  55. data/lib/puma/server.rb +801 -0
  56. data/lib/puma/single.rb +102 -0
  57. data/lib/puma/tcp_logger.rb +32 -0
  58. data/lib/puma/thread_pool.rb +185 -0
  59. data/lib/puma/util.rb +9 -0
  60. data/lib/rack/handler/puma.rb +66 -0
  61. data/test/test_app_status.rb +92 -0
  62. data/test/test_cli.rb +173 -0
  63. data/test/test_config.rb +26 -0
  64. data/test/test_http10.rb +27 -0
  65. data/test/test_http11.rb +144 -0
  66. data/test/test_integration.rb +165 -0
  67. data/test/test_iobuffer.rb +38 -0
  68. data/test/test_minissl.rb +29 -0
  69. data/test/test_null_io.rb +31 -0
  70. data/test/test_persistent.rb +238 -0
  71. data/test/test_puma_server.rb +288 -0
  72. data/test/test_puma_server_ssl.rb +137 -0
  73. data/test/test_rack_handler.rb +10 -0
  74. data/test/test_rack_server.rb +141 -0
  75. data/test/test_tcp_rack.rb +42 -0
  76. data/test/test_thread_pool.rb +156 -0
  77. data/test/test_unix_socket.rb +39 -0
  78. data/test/test_ws.rb +89 -0
  79. data/tools/jungle/README.md +9 -0
  80. data/tools/jungle/init.d/README.md +54 -0
  81. data/tools/jungle/init.d/puma +332 -0
  82. data/tools/jungle/init.d/run-puma +3 -0
  83. data/tools/jungle/upstart/README.md +61 -0
  84. data/tools/jungle/upstart/puma-manager.conf +31 -0
  85. data/tools/jungle/upstart/puma.conf +63 -0
  86. data/tools/trickletest.rb +45 -0
  87. data/wendell-puma.gemspec +55 -0
  88. metadata +225 -0
@@ -0,0 +1,14 @@
1
+ # Standard libraries
2
+ require 'socket'
3
+ require 'tempfile'
4
+ require 'yaml'
5
+ require 'time'
6
+ require 'etc'
7
+ require 'uri'
8
+ require 'stringio'
9
+
10
+ require 'thread'
11
+
12
+ # Ruby Puma
13
+ require 'puma/const'
14
+ require 'puma/server'
@@ -0,0 +1,23 @@
1
+ require 'openssl'
2
+
3
+ module OpenSSL
4
+ module SSL
5
+ class SSLServer
6
+ unless public_method_defined? :accept_nonblock
7
+ def accept_nonblock
8
+ sock = @svr.accept_nonblock
9
+
10
+ begin
11
+ ssl = OpenSSL::SSL::SSLSocket.new(sock, @ctx)
12
+ ssl.sync_close = true
13
+ ssl.accept if @start_immediately
14
+ ssl
15
+ rescue SSLError => ex
16
+ sock.close
17
+ raise ex
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,59 @@
1
+ module Puma
2
+ module App
3
+ class Status
4
+ def initialize(cli)
5
+ @cli = cli
6
+ @auth_token = nil
7
+ end
8
+ OK_STATUS = '{ "status": "ok" }'.freeze
9
+
10
+ attr_accessor :auth_token
11
+
12
+ def authenticate(env)
13
+ return true unless @auth_token
14
+ env['QUERY_STRING'].to_s.split(/&;/).include?("token=#{@auth_token}")
15
+ end
16
+
17
+ def rack_response(status, body, content_type='application/json')
18
+ headers = {
19
+ 'Content-Type' => content_type,
20
+ 'Content-Length' => body.bytesize.to_s
21
+ }
22
+
23
+ [status, headers, [body]]
24
+ end
25
+
26
+ def call(env)
27
+ unless authenticate(env)
28
+ return rack_response(403, 'Invalid auth token', 'text/plain')
29
+ end
30
+
31
+ case env['PATH_INFO']
32
+ when /\/stop$/
33
+ @cli.stop
34
+ return rack_response(200, OK_STATUS)
35
+
36
+ when /\/halt$/
37
+ @cli.halt
38
+ return rack_response(200, OK_STATUS)
39
+
40
+ when /\/restart$/
41
+ @cli.restart
42
+ return rack_response(200, OK_STATUS)
43
+
44
+ when /\/phased-restart$/
45
+ if !@cli.phased_restart
46
+ return rack_response(404, '{ "error": "phased restart not available" }')
47
+ else
48
+ return rack_response(200, OK_STATUS)
49
+ end
50
+
51
+ when /\/stats$/
52
+ return rack_response(200, @cli.stats)
53
+ else
54
+ rack_response 404, "Unsupported action", 'text/plain'
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,298 @@
1
+ require 'puma/const'
2
+
3
+ module Puma
4
+ class Binder
5
+ include Puma::Const
6
+
7
+ def initialize(events)
8
+ @events = events
9
+ @listeners = []
10
+ @inherited_fds = {}
11
+ @unix_paths = []
12
+
13
+ @proto_env = {
14
+ "rack.version".freeze => Rack::VERSION,
15
+ "rack.errors".freeze => events.stderr,
16
+ "rack.multithread".freeze => true,
17
+ "rack.multiprocess".freeze => false,
18
+ "rack.run_once".freeze => false,
19
+ "SCRIPT_NAME".freeze => ENV['SCRIPT_NAME'] || "",
20
+
21
+ # Rack blows up if this is an empty string, and Rack::Lint
22
+ # blows up if it's nil. So 'text/plain' seems like the most
23
+ # sensible default value.
24
+ "CONTENT_TYPE".freeze => "text/plain",
25
+
26
+ "QUERY_STRING".freeze => "",
27
+ SERVER_PROTOCOL => HTTP_11,
28
+ SERVER_SOFTWARE => PUMA_VERSION,
29
+ GATEWAY_INTERFACE => CGI_VER
30
+ }
31
+
32
+ @envs = {}
33
+ @ios = []
34
+ end
35
+
36
+ attr_reader :listeners, :ios
37
+
38
+ def env(sock)
39
+ @envs.fetch(sock, @proto_env)
40
+ end
41
+
42
+ def close
43
+ @ios.each { |i| i.close }
44
+ @unix_paths.each { |i| File.unlink i }
45
+ end
46
+
47
+ def import_from_env
48
+ remove = []
49
+
50
+ ENV.each do |k,v|
51
+ if k =~ /PUMA_INHERIT_\d+/
52
+ fd, url = v.split(":", 2)
53
+ @inherited_fds[url] = fd.to_i
54
+ remove << k
55
+ end
56
+ if k =~ /LISTEN_FDS/ && ENV['LISTEN_PID'].to_i == $$
57
+ v.to_i.times do |num|
58
+ fd = num + 3
59
+ sock = TCPServer.for_fd(fd)
60
+ begin
61
+ url = "unix://" + Socket.unpack_sockaddr_un(sock.getsockname)
62
+ rescue ArgumentError
63
+ port, addr = Socket.unpack_sockaddr_in(sock.getsockname)
64
+ if addr =~ /\:/
65
+ addr = "[#{addr}]"
66
+ end
67
+ url = "tcp://#{addr}:#{port}"
68
+ end
69
+ @inherited_fds[url] = sock
70
+ end
71
+ ENV.delete k
72
+ ENV.delete 'LISTEN_PID'
73
+ end
74
+ end
75
+
76
+ remove.each do |k|
77
+ ENV.delete k
78
+ end
79
+ end
80
+
81
+ def parse(binds, logger)
82
+ binds.each do |str|
83
+ uri = URI.parse str
84
+ case uri.scheme
85
+ when "tcp"
86
+ if fd = @inherited_fds.delete(str)
87
+ logger.log "* Inherited #{str}"
88
+ io = inherit_tcp_listener uri.host, uri.port, fd
89
+ else
90
+ params = Rack::Utils.parse_query uri.query
91
+
92
+ opt = params.key?('low_latency')
93
+ bak = params.fetch('backlog', 1024).to_i
94
+
95
+ logger.log "* Listening on #{str}"
96
+ io = add_tcp_listener uri.host, uri.port, opt, bak
97
+ end
98
+
99
+ @listeners << [str, io]
100
+ when "unix"
101
+ path = "#{uri.host}#{uri.path}"
102
+
103
+ if fd = @inherited_fds.delete(str)
104
+ logger.log "* Inherited #{str}"
105
+ io = inherit_unix_listener path, fd
106
+ else
107
+ logger.log "* Listening on #{str}"
108
+
109
+ umask = nil
110
+
111
+ if uri.query
112
+ params = Rack::Utils.parse_query uri.query
113
+ if u = params['umask']
114
+ # Use Integer() to respect the 0 prefix as octal
115
+ umask = Integer(u)
116
+ end
117
+ end
118
+
119
+ io = add_unix_listener path, umask
120
+ end
121
+
122
+ @listeners << [str, io]
123
+ when "ssl"
124
+ params = Rack::Utils.parse_query uri.query
125
+ require 'puma/minissl'
126
+
127
+ ctx = MiniSSL::Context.new
128
+
129
+ if defined?(JRUBY_VERSION)
130
+ unless params['keystore']
131
+ @events.error "Please specify the Java keystore via 'keystore='"
132
+ end
133
+
134
+ ctx.keystore = params['keystore']
135
+
136
+ unless params['keystore-pass']
137
+ @events.error "Please specify the Java keystore password via 'keystore-pass='"
138
+ end
139
+
140
+ ctx.keystore_pass = params['keystore-pass']
141
+ else
142
+ unless params['key']
143
+ @events.error "Please specify the SSL key via 'key='"
144
+ end
145
+
146
+ ctx.key = params['key']
147
+
148
+ unless params['cert']
149
+ @events.error "Please specify the SSL cert via 'cert='"
150
+ end
151
+
152
+ ctx.cert = params['cert']
153
+ end
154
+
155
+ ctx.verify_mode = MiniSSL::VERIFY_NONE
156
+
157
+ if fd = @inherited_fds.delete(str)
158
+ logger.log "* Inherited #{str}"
159
+ io = inherited_ssl_listener fd, ctx
160
+ else
161
+ logger.log "* Listening on #{str}"
162
+ io = add_ssl_listener uri.host, uri.port, ctx
163
+ end
164
+
165
+ @listeners << [str, io]
166
+ else
167
+ logger.error "Invalid URI: #{str}"
168
+ end
169
+ end
170
+
171
+ # If we inherited fds but didn't use them (because of a
172
+ # configuration change), then be sure to close them.
173
+ @inherited_fds.each do |str, fd|
174
+ logger.log "* Closing unused inherited connection: #{str}"
175
+
176
+ begin
177
+ if fd.kind_of? TCPServer
178
+ fd.close
179
+ else
180
+ IO.for_fd(fd).close
181
+ end
182
+
183
+ rescue SystemCallError
184
+ end
185
+
186
+ # We have to unlink a unix socket path that's not being used
187
+ uri = URI.parse str
188
+ if uri.scheme == "unix"
189
+ path = "#{uri.host}#{uri.path}"
190
+ File.unlink path
191
+ end
192
+ end
193
+
194
+ end
195
+
196
+ # Tell the server to listen on host +host+, port +port+.
197
+ # If +optimize_for_latency+ is true (the default) then clients connecting
198
+ # will be optimized for latency over throughput.
199
+ #
200
+ # +backlog+ indicates how many unaccepted connections the kernel should
201
+ # allow to accumulate before returning connection refused.
202
+ #
203
+ def add_tcp_listener(host, port, optimize_for_latency=true, backlog=1024)
204
+ host = host[1..-2] if host[0..0] == '['
205
+ s = TCPServer.new(host, port)
206
+ if optimize_for_latency
207
+ s.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
208
+ end
209
+ s.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
210
+ s.listen backlog
211
+ @ios << s
212
+ s
213
+ end
214
+
215
+ def inherit_tcp_listener(host, port, fd)
216
+ if fd.kind_of? TCPServer
217
+ s = fd
218
+ else
219
+ s = TCPServer.for_fd(fd)
220
+ end
221
+
222
+ @ios << s
223
+ s
224
+ end
225
+
226
+ def add_ssl_listener(host, port, ctx,
227
+ optimize_for_latency=true, backlog=1024)
228
+ require 'puma/minissl'
229
+
230
+ s = TCPServer.new(host, port)
231
+ if optimize_for_latency
232
+ s.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
233
+ end
234
+ s.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
235
+ s.listen backlog
236
+
237
+ ssl = MiniSSL::Server.new s, ctx
238
+ env = @proto_env.dup
239
+ env[HTTPS_KEY] = HTTPS
240
+ @envs[ssl] = env
241
+
242
+ @ios << ssl
243
+ s
244
+ end
245
+
246
+ def inherited_ssl_listener(fd, ctx)
247
+ require 'puma/minissl'
248
+ s = TCPServer.for_fd(fd)
249
+ @ios << MiniSSL::Server.new(s, ctx)
250
+ s
251
+ end
252
+
253
+ # Tell the server to listen on +path+ as a UNIX domain socket.
254
+ #
255
+ def add_unix_listener(path, umask=nil)
256
+ @unix_paths << path
257
+
258
+ # Let anyone connect by default
259
+ umask ||= 0
260
+
261
+ begin
262
+ old_mask = File.umask(umask)
263
+
264
+ if File.exist? path
265
+ begin
266
+ old = UNIXSocket.new path
267
+ rescue SystemCallError, IOError
268
+ File.unlink path
269
+ else
270
+ old.close
271
+ raise "There is already a server bound to: #{path}"
272
+ end
273
+ end
274
+
275
+ s = UNIXServer.new(path)
276
+ @ios << s
277
+ ensure
278
+ File.umask old_mask
279
+ end
280
+
281
+ s
282
+ end
283
+
284
+ def inherit_unix_listener(path, fd)
285
+ @unix_paths << path
286
+
287
+ if fd.kind_of? TCPServer
288
+ s = fd
289
+ else
290
+ s = UNIXServer.for_fd fd
291
+ end
292
+ @ios << s
293
+
294
+ s
295
+ end
296
+
297
+ end
298
+ end
@@ -0,0 +1,86 @@
1
+ Capistrano::Configuration.instance.load do
2
+
3
+ # Ensure the tmp/sockets directory is created by the deploy:setup task and
4
+ # symlinked in by the deploy:update task. This is not handled by Capistrano
5
+ # v2 but is fixed in v3.
6
+ shared_children.push('tmp/sockets')
7
+
8
+ _cset(:puma_default_hooks) { true }
9
+ _cset(:puma_cmd) { "#{fetch(:bundle_cmd, 'bundle')} exec puma" }
10
+ _cset(:pumactl_cmd) { "#{fetch(:bundle_cmd, 'bundle')} exec pumactl" }
11
+ _cset(:puma_env) { fetch(:rack_env, fetch(:rails_env, 'production')) }
12
+ _cset(:puma_state) { "#{shared_path}/sockets/puma.state" }
13
+ _cset(:puma_socket) { "unix://#{shared_path}/sockets/puma.sock" }
14
+ _cset(:puma_role) { :app }
15
+
16
+ if fetch(:puma_default_hooks)
17
+ after 'deploy:stop', 'puma:stop'
18
+ after 'deploy:start', 'puma:start'
19
+ after 'deploy:restart', 'puma:restart'
20
+ end
21
+
22
+ namespace :puma do
23
+ desc 'Start puma'
24
+ task :start, :roles => lambda { puma_role }, :on_no_matching_servers => :continue do
25
+ run "cd #{current_path} && #{puma_cmd} #{start_options}", :pty => false
26
+ end
27
+
28
+ desc 'Stop puma'
29
+ task :stop, :roles => lambda { puma_role }, :on_no_matching_servers => :continue do
30
+ run "cd #{current_path} && #{pumactl_cmd} -S #{state_path} stop"
31
+ end
32
+
33
+ desc 'Restart puma'
34
+ task :restart, :roles => lambda { puma_role }, :on_no_matching_servers => :continue do
35
+ begin
36
+ run "cd #{current_path} && #{pumactl_cmd} -S #{state_path} restart"
37
+ rescue Capistrano::CommandError => ex
38
+ puts "Failed to restart puma: #{ex}\nAssuming not started."
39
+ start
40
+ end
41
+ end
42
+
43
+ desc 'Restart puma (phased restart)'
44
+ task :phased_restart, :roles => lambda { puma_role }, :on_no_matching_servers => :continue do
45
+ begin
46
+ run "cd #{current_path} && #{pumactl_cmd} -S #{state_path} phased-restart"
47
+ rescue Capistrano::CommandError => ex
48
+ puts "Failed to restart puma: #{ex}\nAssuming not started."
49
+ start
50
+ end
51
+ end
52
+
53
+ end
54
+
55
+ def start_options
56
+ if config_file
57
+ "-q -d -e #{puma_env} -C #{config_file}"
58
+ else
59
+ "-q -d -e #{puma_env} -b '#{puma_socket}' -S #{state_path} --control 'unix://#{shared_path}/sockets/pumactl.sock'"
60
+ end
61
+ end
62
+
63
+ def config_file
64
+ @_config_file ||= begin
65
+ file = fetch(:puma_config_file, nil)
66
+ file = "./config/puma/#{puma_env}.rb" if !file && File.exists?("./config/puma/#{puma_env}.rb")
67
+ file
68
+ end
69
+ end
70
+
71
+ def puma_env
72
+ fetch(:rack_env, fetch(:rails_env, 'production'))
73
+ end
74
+
75
+ def state_path
76
+ (config_file ? configuration.options[:state] : nil) || puma_state
77
+ end
78
+
79
+ def configuration
80
+ require 'puma/configuration'
81
+
82
+ config = Puma::Configuration.new(:config_file => config_file)
83
+ config.load
84
+ config
85
+ end
86
+ end