puma 2.7.0 → 3.1.1

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 (79) hide show
  1. checksums.yaml +5 -13
  2. data/DEPLOYMENT.md +91 -0
  3. data/Gemfile +3 -2
  4. data/History.txt +624 -1
  5. data/Manifest.txt +15 -3
  6. data/README.md +129 -14
  7. data/Rakefile +3 -3
  8. data/bin/puma-wild +31 -0
  9. data/bin/pumactl +1 -1
  10. data/docs/nginx.md +1 -1
  11. data/docs/signals.md +43 -0
  12. data/ext/puma_http11/extconf.rb +7 -2
  13. data/ext/puma_http11/http11_parser.java.rl +5 -5
  14. data/ext/puma_http11/io_buffer.c +1 -1
  15. data/ext/puma_http11/mini_ssl.c +233 -18
  16. data/ext/puma_http11/org/jruby/puma/Http11.java +12 -3
  17. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +39 -39
  18. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +245 -195
  19. data/ext/puma_http11/puma_http11.c +12 -4
  20. data/lib/puma.rb +1 -0
  21. data/lib/puma/app/status.rb +7 -0
  22. data/lib/puma/binder.rb +108 -39
  23. data/lib/puma/capistrano.rb +23 -6
  24. data/lib/puma/cli.rb +141 -446
  25. data/lib/puma/client.rb +48 -1
  26. data/lib/puma/cluster.rb +207 -58
  27. data/lib/puma/commonlogger.rb +107 -0
  28. data/lib/puma/configuration.rb +262 -235
  29. data/lib/puma/const.rb +97 -14
  30. data/lib/puma/control_cli.rb +85 -77
  31. data/lib/puma/convenient.rb +23 -0
  32. data/lib/puma/daemon_ext.rb +11 -4
  33. data/lib/puma/detect.rb +8 -1
  34. data/lib/puma/dsl.rb +456 -0
  35. data/lib/puma/events.rb +35 -18
  36. data/lib/puma/jruby_restart.rb +1 -1
  37. data/lib/puma/launcher.rb +399 -0
  38. data/lib/puma/minissl.rb +49 -20
  39. data/lib/puma/null_io.rb +15 -0
  40. data/lib/puma/plugin.rb +104 -0
  41. data/lib/puma/plugin/tmp_restart.rb +35 -0
  42. data/lib/puma/rack/backports/uri/common_18.rb +56 -0
  43. data/lib/puma/rack/backports/uri/common_192.rb +52 -0
  44. data/lib/puma/rack/backports/uri/common_193.rb +29 -0
  45. data/lib/puma/rack/builder.rb +295 -0
  46. data/lib/puma/rack/urlmap.rb +90 -0
  47. data/lib/puma/reactor.rb +14 -1
  48. data/lib/puma/runner.rb +35 -17
  49. data/lib/puma/server.rb +161 -58
  50. data/lib/puma/single.rb +15 -10
  51. data/lib/puma/state_file.rb +29 -0
  52. data/lib/puma/thread_pool.rb +88 -13
  53. data/lib/puma/util.rb +123 -0
  54. data/lib/rack/handler/puma.rb +35 -29
  55. data/puma.gemspec +2 -4
  56. data/tools/jungle/init.d/README.md +2 -2
  57. data/tools/jungle/init.d/puma +69 -7
  58. data/tools/jungle/upstart/puma.conf +8 -2
  59. metadata +51 -71
  60. data/COPYING +0 -55
  61. data/TODO +0 -5
  62. data/lib/puma/rack_patch.rb +0 -45
  63. data/test/test_app_status.rb +0 -92
  64. data/test/test_cli.rb +0 -173
  65. data/test/test_config.rb +0 -16
  66. data/test/test_http10.rb +0 -27
  67. data/test/test_http11.rb +0 -145
  68. data/test/test_integration.rb +0 -165
  69. data/test/test_iobuffer.rb +0 -38
  70. data/test/test_minissl.rb +0 -25
  71. data/test/test_null_io.rb +0 -31
  72. data/test/test_persistent.rb +0 -238
  73. data/test/test_puma_server.rb +0 -292
  74. data/test/test_rack_handler.rb +0 -10
  75. data/test/test_rack_server.rb +0 -141
  76. data/test/test_tcp_rack.rb +0 -42
  77. data/test/test_thread_pool.rb +0 -156
  78. data/test/test_unix_socket.rb +0 -39
  79. data/test/test_ws.rb +0 -89
data/lib/puma/const.rb CHANGED
@@ -1,5 +1,4 @@
1
- require 'rack'
2
-
1
+ #encoding: utf-8
3
2
  module Puma
4
3
  class UnsupportedOption < RuntimeError
5
4
  end
@@ -8,12 +7,85 @@ module Puma
8
7
  # Every standard HTTP code mapped to the appropriate message. These are
9
8
  # used so frequently that they are placed directly in Puma for easy
10
9
  # access rather than Puma::Const itself.
11
- HTTP_STATUS_CODES = Rack::Utils::HTTP_STATUS_CODES
10
+
11
+ # Every standard HTTP code mapped to the appropriate message.
12
+ # Generated with:
13
+ # curl -s https://www.iana.org/assignments/http-status-codes/http-status-codes-1.csv | \
14
+ # ruby -ne 'm = /^(\d{3}),(?!Unassigned|\(Unused\))([^,]+)/.match($_) and \
15
+ # puts "#{m[1]} => \x27#{m[2].strip}\x27,"'
16
+ HTTP_STATUS_CODES = {
17
+ 100 => 'Continue',
18
+ 101 => 'Switching Protocols',
19
+ 102 => 'Processing',
20
+ 200 => 'OK',
21
+ 201 => 'Created',
22
+ 202 => 'Accepted',
23
+ 203 => 'Non-Authoritative Information',
24
+ 204 => 'No Content',
25
+ 205 => 'Reset Content',
26
+ 206 => 'Partial Content',
27
+ 207 => 'Multi-Status',
28
+ 208 => 'Already Reported',
29
+ 226 => 'IM Used',
30
+ 300 => 'Multiple Choices',
31
+ 301 => 'Moved Permanently',
32
+ 302 => 'Found',
33
+ 303 => 'See Other',
34
+ 304 => 'Not Modified',
35
+ 305 => 'Use Proxy',
36
+ 307 => 'Temporary Redirect',
37
+ 308 => 'Permanent Redirect',
38
+ 400 => 'Bad Request',
39
+ 401 => 'Unauthorized',
40
+ 402 => 'Payment Required',
41
+ 403 => 'Forbidden',
42
+ 404 => 'Not Found',
43
+ 405 => 'Method Not Allowed',
44
+ 406 => 'Not Acceptable',
45
+ 407 => 'Proxy Authentication Required',
46
+ 408 => 'Request Timeout',
47
+ 409 => 'Conflict',
48
+ 410 => 'Gone',
49
+ 411 => 'Length Required',
50
+ 412 => 'Precondition Failed',
51
+ 413 => 'Payload Too Large',
52
+ 414 => 'URI Too Long',
53
+ 415 => 'Unsupported Media Type',
54
+ 416 => 'Range Not Satisfiable',
55
+ 417 => 'Expectation Failed',
56
+ 422 => 'Unprocessable Entity',
57
+ 423 => 'Locked',
58
+ 424 => 'Failed Dependency',
59
+ 426 => 'Upgrade Required',
60
+ 428 => 'Precondition Required',
61
+ 429 => 'Too Many Requests',
62
+ 431 => 'Request Header Fields Too Large',
63
+ 500 => 'Internal Server Error',
64
+ 501 => 'Not Implemented',
65
+ 502 => 'Bad Gateway',
66
+ 503 => 'Service Unavailable',
67
+ 504 => 'Gateway Timeout',
68
+ 505 => 'HTTP Version Not Supported',
69
+ 506 => 'Variant Also Negotiates',
70
+ 507 => 'Insufficient Storage',
71
+ 508 => 'Loop Detected',
72
+ 510 => 'Not Extended',
73
+ 511 => 'Network Authentication Required'
74
+ }
75
+
76
+ SYMBOL_TO_STATUS_CODE = Hash[*HTTP_STATUS_CODES.map { |code, message|
77
+ [message.downcase.gsub(/\s|-|'/, '_').to_sym, code]
78
+ }.flatten]
12
79
 
13
80
  # For some HTTP status codes the client only expects headers.
14
- STATUS_WITH_NO_ENTITY_BODY = Hash[Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.map { |s|
15
- [s, true]
16
- }]
81
+ #
82
+
83
+ no_body = {}
84
+ ((100..199).to_a << 204 << 205 << 304).each do |code|
85
+ no_body[code] = true
86
+ end
87
+
88
+ STATUS_WITH_NO_ENTITY_BODY = no_body
17
89
 
18
90
  # Frequently used constants when constructing requests or responses. Many times
19
91
  # the constant just refers to a string with the same contents. Using these constants
@@ -28,9 +100,10 @@ module Puma
28
100
  # too taxing on performance.
29
101
  module Const
30
102
 
31
- PUMA_VERSION = VERSION = "2.7.0".freeze
32
- CODE_NAME = "Earl of Sandwich Partition"
33
-
103
+ PUMA_VERSION = VERSION = "3.1.1".freeze
104
+ CODE_NAME = "El Niño Winter Wonderland".freeze
105
+ PUMA_SERVER_STRING = ['puma', PUMA_VERSION, CODE_NAME].join(' ').freeze
106
+
34
107
  FAST_TRACK_KA_TIMEOUT = 0.2
35
108
 
36
109
  # The default number of seconds for another request within a persistent
@@ -41,6 +114,10 @@ module Puma
41
114
  # for the request
42
115
  FIRST_DATA_TIMEOUT = 30
43
116
 
117
+ # How long to wait when getting some write blocking on the socket when
118
+ # sending data back
119
+ WRITE_TIMEOUT = 10
120
+
44
121
  DATE = "Date".freeze
45
122
 
46
123
  SCRIPT_NAME = "SCRIPT_NAME".freeze
@@ -54,15 +131,18 @@ module Puma
54
131
  PUMA_TMP_BASE = "puma".freeze
55
132
 
56
133
  # Indicate that we couldn't parse the request
57
- ERROR_400_RESPONSE = "HTTP/1.1 400 Bad Request\r\n\r\n"
134
+ ERROR_400_RESPONSE = "HTTP/1.1 400 Bad Request\r\n\r\n".freeze
58
135
 
59
136
  # The standard empty 404 response for bad requests. Use Error4040Handler for custom stuff.
60
137
  ERROR_404_RESPONSE = "HTTP/1.1 404 Not Found\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\nNOT FOUND".freeze
61
138
 
139
+ # The standard empty 408 response for requests that timed out.
140
+ ERROR_408_RESPONSE = "HTTP/1.1 408 Request Timeout\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\n".freeze
141
+
62
142
  CONTENT_LENGTH = "CONTENT_LENGTH".freeze
63
143
 
64
144
  # Indicate that there was an internal error, obviously.
65
- ERROR_500_RESPONSE = "HTTP/1.1 500 Internal Server Error\r\n\r\n"
145
+ ERROR_500_RESPONSE = "HTTP/1.1 500 Internal Server Error\r\n\r\n".freeze
66
146
 
67
147
  # A common header for indicating the server is too busy. Not used yet.
68
148
  ERROR_503_RESPONSE = "HTTP/1.1 503 Service Unavailable\r\n\r\nBUSY".freeze
@@ -104,6 +184,8 @@ module Puma
104
184
  PORT_80 = "80".freeze
105
185
  PORT_443 = "443".freeze
106
186
  LOCALHOST = "localhost".freeze
187
+ LOCALHOST_IP = "127.0.0.1".freeze
188
+ LOCALHOST_ADDR = "127.0.0.1:0".freeze
107
189
 
108
190
  SERVER_PROTOCOL = "SERVER_PROTOCOL".freeze
109
191
  HTTP_11 = "HTTP/1.1".freeze
@@ -122,6 +204,7 @@ module Puma
122
204
  RACK_AFTER_REPLY = "rack.after_reply".freeze
123
205
  PUMA_SOCKET = "puma.socket".freeze
124
206
  PUMA_CONFIG = "puma.config".freeze
207
+ PUMA_PEERCERT = "puma.peercert".freeze
125
208
 
126
209
  HTTP = "http".freeze
127
210
  HTTPS = "https".freeze
@@ -135,11 +218,11 @@ module Puma
135
218
  HTTP_10_200 = "HTTP/1.0 200 OK\r\n".freeze
136
219
 
137
220
  CLOSE = "close".freeze
138
- KEEP_ALIVE = "Keep-Alive".freeze
221
+ KEEP_ALIVE = "keep-alive".freeze
139
222
 
140
- CONTENT_LENGTH2 = "Content-Length".freeze
223
+ CONTENT_LENGTH2 = "content-length".freeze
141
224
  CONTENT_LENGTH_S = "Content-Length: ".freeze
142
- TRANSFER_ENCODING = "Transfer-Encoding".freeze
225
+ TRANSFER_ENCODING = "transfer-encoding".freeze
143
226
 
144
227
  CONNECTION_CLOSE = "Connection: close\r\n".freeze
145
228
  CONNECTION_KEEP_ALIVE = "Connection: Keep-Alive\r\n".freeze
@@ -1,53 +1,60 @@
1
1
  require 'optparse'
2
+ require 'puma'
2
3
  require 'puma/const'
4
+ require 'puma/detect'
3
5
  require 'puma/configuration'
4
- require 'yaml'
5
6
  require 'uri'
6
7
  require 'socket'
8
+
7
9
  module Puma
8
10
  class ControlCLI
9
11
 
10
- COMMANDS = %w{halt restart phased-restart start stats status stop}
11
-
12
- def is_windows?
13
- RUBY_PLATFORM =~ /(win|w)32$/ ? true : false
14
- end
12
+ COMMANDS = %w{halt restart phased-restart start stats status stop reload-worker-directory}
15
13
 
16
14
  def initialize(argv, stdout=STDOUT, stderr=STDERR)
17
- @argv = argv
15
+ @state = nil
16
+ @quiet = false
17
+ @pidfile = nil
18
+ @pid = nil
19
+ @control_url = nil
20
+ @control_auth_token = nil
21
+ @config_file = nil
22
+ @command = nil
23
+
24
+ @argv = argv.dup
18
25
  @stdout = stdout
19
26
  @stderr = stderr
20
- @options = {}
27
+ @cli_options = {}
21
28
 
22
29
  opts = OptionParser.new do |o|
23
30
  o.banner = "Usage: pumactl (-p PID | -P pidfile | -S status_file | -C url -T token | -F config.rb) (#{COMMANDS.join("|")})"
24
31
 
25
32
  o.on "-S", "--state PATH", "Where the state file to use is" do |arg|
26
- @options[:state] = arg
33
+ @state = arg
27
34
  end
28
35
 
29
36
  o.on "-Q", "--quiet", "Not display messages" do |arg|
30
- @options[:quiet_flag] = true
37
+ @quiet = true
31
38
  end
32
39
 
33
40
  o.on "-P", "--pidfile PATH", "Pid file" do |arg|
34
- @options[:pidfile] = arg
41
+ @pidfile = arg
35
42
  end
36
43
 
37
44
  o.on "-p", "--pid PID", "Pid" do |arg|
38
- @options[:pid] = arg.to_i
45
+ @pid = arg.to_i
39
46
  end
40
47
 
41
48
  o.on "-C", "--control-url URL", "The bind url to use for the control server" do |arg|
42
- @options[:control_url] = arg
49
+ @control_url = arg
43
50
  end
44
51
 
45
52
  o.on "-T", "--control-token TOKEN", "The token to use as authentication for the control server" do |arg|
46
- @options[:control_auth_token] = arg
53
+ @control_auth_token = arg
47
54
  end
48
55
 
49
56
  o.on "-F", "--config-file PATH", "Puma config script" do |arg|
50
- @options[:config_file] = arg
57
+ @config_file = arg
51
58
  end
52
59
 
53
60
  o.on_tail("-H", "--help", "Show this message") do
@@ -63,18 +70,29 @@ module Puma
63
70
 
64
71
  opts.order!(argv) { |a| opts.terminate a }
65
72
 
66
- command = argv.shift
67
- @options[:command] = command if command
73
+ @command = argv.shift
74
+
75
+ unless @config_file == '-'
76
+ if @config_file.nil? and File.exist?('config/puma.rb')
77
+ @config_file = 'config/puma.rb'
78
+ end
68
79
 
69
- Puma::Configuration.new(@options).load if @options[:config_file]
80
+ if @config_file
81
+ config = Puma::Configuration.from_file @config_file
82
+ @state ||= config.options[:state]
83
+ @control_url ||= config.options[:control_url]
84
+ @control_auth_token ||= config.options[:control_auth_token]
85
+ @pidfile ||= config.options[:pidfile]
86
+ end
87
+ end
70
88
 
71
89
  # check present of command
72
- unless @options[:command]
90
+ unless @command
73
91
  raise "Available commands: #{COMMANDS.join(", ")}"
74
92
  end
75
93
 
76
- unless COMMANDS.include? @options[:command]
77
- raise "Invalid command: #{@options[:command]}"
94
+ unless COMMANDS.include? @command
95
+ raise "Invalid command: #{@command}"
78
96
  end
79
97
 
80
98
  rescue => e
@@ -83,45 +101,29 @@ module Puma
83
101
  end
84
102
 
85
103
  def message(msg)
86
- @stdout.puts msg unless @options[:quiet_flag]
104
+ @stdout.puts msg unless @quiet
87
105
  end
88
106
 
89
107
  def prepare_configuration
90
- if @options.has_key? :state
91
- unless File.exist? @options[:state]
92
- raise "Status file not found: #{@options[:state]}"
108
+ if @state
109
+ unless File.exist? @state
110
+ raise "State file not found: #{@state}"
93
111
  end
94
112
 
95
- status = YAML.load File.read(@options[:state])
96
-
97
- if status.kind_of?(Hash) && status.has_key?("config")
98
-
99
- conf = status["config"]
100
-
101
- # get control_url
102
- if url = conf.options[:control_url]
103
- @options[:control_url] = url
104
- end
113
+ sf = Puma::StateFile.new
114
+ sf.load @state
105
115
 
106
- # get control_auth_token
107
- if token = conf.options[:control_auth_token]
108
- @options[:control_auth_token] = token
109
- end
110
-
111
- # get pid
112
- @options[:pid] = status["pid"].to_i
113
- else
114
- raise "Invalid status file: #{@options[:state]}"
115
- end
116
-
117
- elsif @options.has_key? :pidfile
116
+ @control_url = sf.control_url
117
+ @control_auth_token = sf.control_auth_token
118
+ @pid = sf.pid
119
+ elsif @pidfile
118
120
  # get pid from pid_file
119
- @options[:pid] = File.open(@options[:pidfile]).gets.to_i
121
+ @pid = File.open(@pidfile).gets.to_i
120
122
  end
121
123
  end
122
124
 
123
125
  def send_request
124
- uri = URI.parse @options[:control_url]
126
+ uri = URI.parse @control_url
125
127
 
126
128
  # create server object by scheme
127
129
  @server = case uri.scheme
@@ -133,13 +135,13 @@ module Puma
133
135
  raise "Invalid scheme: #{uri.scheme}"
134
136
  end
135
137
 
136
- if @options[:command] == "status"
138
+ if @command == "status"
137
139
  message "Puma is started"
138
140
  else
139
- url = "/#{@options[:command]}"
141
+ url = "/#{@command}"
140
142
 
141
- if @options.has_key?(:control_auth_token)
142
- url = url + "?token=#{@options[:control_auth_token]}"
143
+ if @control_auth_token
144
+ url = url + "?token=#{@control_auth_token}"
143
145
  end
144
146
 
145
147
  @server << "GET #{url} HTTP/1.0\r\n\r\n"
@@ -164,63 +166,66 @@ module Puma
164
166
  raise "Bad response from server: #{@code}"
165
167
  end
166
168
 
167
- message "Command #{@options[:command]} sent success"
168
- message response.last if @options[:command] == "stats"
169
+ message "Command #{@command} sent success"
170
+ message response.last if @command == "stats"
169
171
  end
170
172
 
171
173
  @server.close
172
174
  end
173
175
 
174
176
  def send_signal
175
- unless pid = @options[:pid]
177
+ unless @pid
176
178
  raise "Neither pid nor control url available"
177
179
  end
178
180
 
179
181
  begin
180
- Process.getpgid pid
182
+ Process.getpgid @pid
181
183
  rescue SystemCallError
182
- if @options[:command] == "restart"
183
- @options.delete(:command)
184
+ if @command == "restart"
184
185
  start
185
186
  else
186
- raise "No pid '#{pid}' found"
187
+ raise "No pid '#{@pid}' found"
187
188
  end
188
189
  end
189
190
 
190
- case @options[:command]
191
+ case @command
191
192
  when "restart"
192
- Process.kill "SIGUSR2", pid
193
+ Process.kill "SIGUSR2", @pid
193
194
 
194
195
  when "halt"
195
- Process.kill "QUIT", pid
196
+ Process.kill "QUIT", @pid
196
197
 
197
198
  when "stop"
198
- Process.kill "SIGTERM", pid
199
+ Process.kill "SIGTERM", @pid
199
200
 
200
201
  when "stats"
201
202
  puts "Stats not available via pid only"
202
203
  return
203
204
 
205
+ when "reload-worker-directory"
206
+ puts "reload-worker-directory not available via pid only"
207
+ return
208
+
204
209
  when "phased-restart"
205
- Process.kill "SIGUSR1", pid
210
+ Process.kill "SIGUSR1", @pid
206
211
 
207
212
  else
208
213
  message "Puma is started"
209
214
  return
210
215
  end
211
216
 
212
- message "Command #{@options[:command]} sent success"
217
+ message "Command #{@command} sent success"
213
218
  end
214
219
 
215
220
  def run
216
- start if @options[:command] == "start"
221
+ start if @command == "start"
217
222
 
218
223
  prepare_configuration
219
224
 
220
- if is_windows?
225
+ if Puma.windows?
221
226
  send_request
222
227
  else
223
- @options.has_key?(:control_url) ? send_request : send_signal
228
+ @control_url ? send_request : send_signal
224
229
  end
225
230
 
226
231
  rescue => e
@@ -232,18 +237,21 @@ module Puma
232
237
  def start
233
238
  require 'puma/cli'
234
239
 
235
- run_args = @argv
240
+ run_args = []
236
241
 
237
- if path = @options[:state]
238
- run_args = ["-S", path] + run_args
239
- end
240
-
241
- if path = @options[:config_file]
242
- run_args = ["-C", path] + run_args
243
- end
242
+ run_args += ["-S", @state] if @state
243
+ run_args += ["-q"] if @quiet
244
+ run_args += ["--pidfile", @pidfile] if @pidfile
245
+ run_args += ["--control", @control_url] if @control_url
246
+ run_args += ["--control-token", @control_auth_token] if @control_auth_token
247
+ run_args += ["-C", @config_file] if @config_file
244
248
 
245
249
  events = Puma::Events.new @stdout, @stderr
246
250
 
251
+ # replace $0 because puma use it to generate restart command
252
+ puma_cmd = $0.gsub(/pumactl$/, 'puma')
253
+ $0 = puma_cmd if File.exist?(puma_cmd)
254
+
247
255
  cli = Puma::CLI.new run_args, events
248
256
  cli.run
249
257
  end