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,185 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'puma/server'
4
+ require 'puma/const'
5
+
6
+ module Puma
7
+ # Generic class that is used by `Puma::Cluster` and `Puma::Single` to
8
+ # serve requests. This class spawns a new instance of `Puma::Server` via
9
+ # a call to `start_server`.
10
+ class Runner
11
+ def initialize(cli, events)
12
+ @launcher = cli
13
+ @events = events
14
+ @options = cli.options
15
+ @app = nil
16
+ @control = nil
17
+ @started_at = Time.now
18
+ end
19
+
20
+ def daemon?
21
+ @options[:daemon]
22
+ end
23
+
24
+ def development?
25
+ @options[:environment] == "development"
26
+ end
27
+
28
+ def test?
29
+ @options[:environment] == "test"
30
+ end
31
+
32
+ def log(str)
33
+ @events.log str
34
+ end
35
+
36
+ def before_restart
37
+ @control.stop(true) if @control
38
+ end
39
+
40
+ def error(str)
41
+ @events.error str
42
+ end
43
+
44
+ def debug(str)
45
+ @events.log "- #{str}" if @options[:debug]
46
+ end
47
+
48
+ def start_control
49
+ str = @options[:control_url]
50
+ return unless str
51
+
52
+ require 'puma/app/status'
53
+
54
+ uri = URI.parse str
55
+
56
+ if token = @options[:control_auth_token]
57
+ token = nil if token.empty? || token == 'none'
58
+ end
59
+
60
+ app = Puma::App::Status.new @launcher, token
61
+
62
+ control = Puma::Server.new app, @launcher.events
63
+ control.min_threads = 0
64
+ control.max_threads = 1
65
+
66
+ case uri.scheme
67
+ when "tcp"
68
+ log "* Starting control server on #{str}"
69
+ control.add_tcp_listener uri.host, uri.port
70
+ when "unix"
71
+ log "* Starting control server on #{str}"
72
+ path = "#{uri.host}#{uri.path}"
73
+ mask = @options[:control_url_umask]
74
+
75
+ control.add_unix_listener path, mask
76
+ else
77
+ error "Invalid control URI: #{str}"
78
+ end
79
+
80
+ control.run
81
+ @control = control
82
+ end
83
+
84
+ def ruby_engine
85
+ if !defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby"
86
+ "ruby #{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}"
87
+ else
88
+ if defined?(RUBY_ENGINE_VERSION)
89
+ "#{RUBY_ENGINE} #{RUBY_ENGINE_VERSION} - ruby #{RUBY_VERSION}"
90
+ else
91
+ "#{RUBY_ENGINE} #{RUBY_VERSION}"
92
+ end
93
+ end
94
+ end
95
+
96
+ def output_header(mode)
97
+ min_t = @options[:min_threads]
98
+ max_t = @options[:max_threads]
99
+
100
+ log "Puma starting in #{mode} mode..."
101
+ log "* Version #{Puma::Const::PUMA_VERSION} (#{ruby_engine}), codename: #{Puma::Const::CODE_NAME}"
102
+ log "* Min threads: #{min_t}, max threads: #{max_t}"
103
+ log "* Environment: #{ENV['RACK_ENV']}"
104
+
105
+ if @options[:mode] == :tcp
106
+ log "* Mode: Lopez Express (tcp)"
107
+ end
108
+ end
109
+
110
+ def redirected_io?
111
+ @options[:redirect_stdout] || @options[:redirect_stderr]
112
+ end
113
+
114
+ def redirect_io
115
+ stdout = @options[:redirect_stdout]
116
+ stderr = @options[:redirect_stderr]
117
+ append = @options[:redirect_append]
118
+
119
+ if stdout
120
+ unless Dir.exist?(File.dirname(stdout))
121
+ raise "Cannot redirect STDOUT to #{stdout}"
122
+ end
123
+
124
+ STDOUT.reopen stdout, (append ? "a" : "w")
125
+ STDOUT.sync = true
126
+ STDOUT.puts "=== puma startup: #{Time.now} ==="
127
+ end
128
+
129
+ if stderr
130
+ unless Dir.exist?(File.dirname(stderr))
131
+ raise "Cannot redirect STDERR to #{stderr}"
132
+ end
133
+
134
+ STDERR.reopen stderr, (append ? "a" : "w")
135
+ STDERR.sync = true
136
+ STDERR.puts "=== puma startup: #{Time.now} ==="
137
+ end
138
+ end
139
+
140
+ def load_and_bind
141
+ unless @launcher.config.app_configured?
142
+ error "No application configured, nothing to run"
143
+ exit 1
144
+ end
145
+
146
+ # Load the app before we daemonize.
147
+ begin
148
+ @app = @launcher.config.app
149
+ rescue Exception => e
150
+ log "! Unable to load application: #{e.class}: #{e.message}"
151
+ raise e
152
+ end
153
+
154
+ @launcher.binder.parse @options[:binds], self
155
+ end
156
+
157
+ def app
158
+ @app ||= @launcher.config.app
159
+ end
160
+
161
+ def start_server
162
+ min_t = @options[:min_threads]
163
+ max_t = @options[:max_threads]
164
+
165
+ server = Puma::Server.new app, @launcher.events, @options
166
+ server.min_threads = min_t
167
+ server.max_threads = max_t
168
+ server.inherit_binder @launcher.binder
169
+
170
+ if @options[:mode] == :tcp
171
+ server.tcp_mode!
172
+ end
173
+
174
+ if @options[:early_hints]
175
+ server.early_hints = true
176
+ end
177
+
178
+ unless development? || test?
179
+ server.leak_stack_on_error = false
180
+ end
181
+
182
+ server
183
+ end
184
+ end
185
+ end
@@ -0,0 +1,1033 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'stringio'
4
+
5
+ require 'puma/thread_pool'
6
+ require 'puma/const'
7
+ require 'puma/events'
8
+ require 'puma/null_io'
9
+ require 'puma/reactor'
10
+ require 'puma/client'
11
+ require 'puma/binder'
12
+ require 'puma/delegation'
13
+ require 'puma/accept_nonblock'
14
+ require 'puma/util'
15
+
16
+ require 'puma/puma_http11'
17
+
18
+ require 'socket'
19
+
20
+ module Puma
21
+
22
+ # The HTTP Server itself. Serves out a single Rack app.
23
+ #
24
+ # This class is used by the `Puma::Single` and `Puma::Cluster` classes
25
+ # to generate one or more `Puma::Server` instances capable of handling requests.
26
+ # Each Puma process will contain one `Puma::Server` instance.
27
+ #
28
+ # The `Puma::Server` instance pulls requests from the socket, adds them to a
29
+ # `Puma::Reactor` where they get eventually passed to a `Puma::ThreadPool`.
30
+ #
31
+ # Each `Puma::Server` will have one reactor and one thread pool.
32
+ class Server
33
+
34
+ include Puma::Const
35
+ extend Puma::Delegation
36
+
37
+ attr_reader :thread
38
+ attr_reader :events
39
+ attr_accessor :app
40
+
41
+ attr_accessor :min_threads
42
+ attr_accessor :max_threads
43
+ attr_accessor :persistent_timeout
44
+ attr_accessor :auto_trim_time
45
+ attr_accessor :reaping_time
46
+ attr_accessor :first_data_timeout
47
+
48
+ # Create a server for the rack app +app+.
49
+ #
50
+ # +events+ is an object which will be called when certain error events occur
51
+ # to be handled. See Puma::Events for the list of current methods to implement.
52
+ #
53
+ # Server#run returns a thread that you can join on to wait for the server
54
+ # to do its work.
55
+ #
56
+ def initialize(app, events=Events.stdio, options={})
57
+ @app = app
58
+ @events = events
59
+
60
+ @check, @notify = Puma::Util.pipe
61
+
62
+ @status = :stop
63
+
64
+ @min_threads = 0
65
+ @max_threads = 16
66
+ @auto_trim_time = 30
67
+ @reaping_time = 1
68
+
69
+ @thread = nil
70
+ @thread_pool = nil
71
+ @early_hints = nil
72
+
73
+ @persistent_timeout = options.fetch(:persistent_timeout, PERSISTENT_TIMEOUT)
74
+ @first_data_timeout = options.fetch(:first_data_timeout, FIRST_DATA_TIMEOUT)
75
+
76
+ @binder = Binder.new(events)
77
+
78
+ @leak_stack_on_error = true
79
+
80
+ @options = options
81
+ @queue_requests = options[:queue_requests].nil? ? true : options[:queue_requests]
82
+
83
+ ENV['RACK_ENV'] ||= "development"
84
+
85
+ @mode = :http
86
+
87
+ @precheck_closing = true
88
+ end
89
+
90
+ attr_accessor :binder, :leak_stack_on_error, :early_hints
91
+
92
+ forward :add_tcp_listener, :@binder
93
+ forward :add_ssl_listener, :@binder
94
+ forward :add_unix_listener, :@binder
95
+ forward :connected_port, :@binder
96
+
97
+ def inherit_binder(bind)
98
+ @binder = bind
99
+ end
100
+
101
+ def tcp_mode!
102
+ @mode = :tcp
103
+ end
104
+
105
+ # On Linux, use TCP_CORK to better control how the TCP stack
106
+ # packetizes our stream. This improves both latency and throughput.
107
+ #
108
+ if RUBY_PLATFORM =~ /linux/
109
+ UNPACK_TCP_STATE_FROM_TCP_INFO = "C".freeze
110
+
111
+ # 6 == Socket::IPPROTO_TCP
112
+ # 3 == TCP_CORK
113
+ # 1/0 == turn on/off
114
+ def cork_socket(socket)
115
+ begin
116
+ socket.setsockopt(6, 3, 1) if socket.kind_of? TCPSocket
117
+ rescue IOError, SystemCallError
118
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
119
+ end
120
+ end
121
+
122
+ def uncork_socket(socket)
123
+ begin
124
+ socket.setsockopt(6, 3, 0) if socket.kind_of? TCPSocket
125
+ rescue IOError, SystemCallError
126
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
127
+ end
128
+ end
129
+
130
+ def closed_socket?(socket)
131
+ return false unless socket.kind_of? TCPSocket
132
+ return false unless @precheck_closing
133
+
134
+ begin
135
+ tcp_info = socket.getsockopt(Socket::SOL_TCP, Socket::TCP_INFO)
136
+ rescue IOError, SystemCallError
137
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
138
+ @precheck_closing = false
139
+ false
140
+ else
141
+ state = tcp_info.unpack(UNPACK_TCP_STATE_FROM_TCP_INFO)[0]
142
+ # TIME_WAIT: 6, CLOSE: 7, CLOSE_WAIT: 8, LAST_ACK: 9, CLOSING: 11
143
+ (state >= 6 && state <= 9) || state == 11
144
+ end
145
+ end
146
+ else
147
+ def cork_socket(socket)
148
+ end
149
+
150
+ def uncork_socket(socket)
151
+ end
152
+
153
+ def closed_socket?(socket)
154
+ false
155
+ end
156
+ end
157
+
158
+ def backlog
159
+ @thread_pool and @thread_pool.backlog
160
+ end
161
+
162
+ def running
163
+ @thread_pool and @thread_pool.spawned
164
+ end
165
+
166
+
167
+ # This number represents the number of requests that
168
+ # the server is capable of taking right now.
169
+ #
170
+ # For example if the number is 5 then it means
171
+ # there are 5 threads sitting idle ready to take
172
+ # a request. If one request comes in, then the
173
+ # value would be 4 until it finishes processing.
174
+ def pool_capacity
175
+ @thread_pool and @thread_pool.pool_capacity
176
+ end
177
+
178
+ # Lopez Mode == raw tcp apps
179
+
180
+ def run_lopez_mode(background=true)
181
+ @thread_pool = ThreadPool.new(@min_threads,
182
+ @max_threads,
183
+ Hash) do |client, tl|
184
+
185
+ io = client.to_io
186
+ addr = io.peeraddr.last
187
+
188
+ if addr.empty?
189
+ # Set unix socket addrs to localhost
190
+ addr = "127.0.0.1:0"
191
+ else
192
+ addr = "#{addr}:#{io.peeraddr[1]}"
193
+ end
194
+
195
+ env = { 'thread' => tl, REMOTE_ADDR => addr }
196
+
197
+ begin
198
+ @app.call env, client.to_io
199
+ rescue Object => e
200
+ STDERR.puts "! Detected exception at toplevel: #{e.message} (#{e.class})"
201
+ STDERR.puts e.backtrace
202
+ end
203
+
204
+ client.close unless env['detach']
205
+ end
206
+
207
+ @events.fire :state, :running
208
+
209
+ if background
210
+ @thread = Thread.new do
211
+ Puma.set_thread_name "server"
212
+ handle_servers_lopez_mode
213
+ end
214
+ return @thread
215
+ else
216
+ handle_servers_lopez_mode
217
+ end
218
+ end
219
+
220
+ def handle_servers_lopez_mode
221
+ begin
222
+ check = @check
223
+ sockets = [check] + @binder.ios
224
+ pool = @thread_pool
225
+
226
+ while @status == :run
227
+ begin
228
+ ios = IO.select sockets
229
+ ios.first.each do |sock|
230
+ if sock == check
231
+ break if handle_check
232
+ else
233
+ begin
234
+ if io = sock.accept_nonblock
235
+ client = Client.new io, nil
236
+ pool << client
237
+ end
238
+ rescue SystemCallError
239
+ # nothing
240
+ rescue Errno::ECONNABORTED
241
+ # client closed the socket even before accept
242
+ begin
243
+ io.close
244
+ rescue
245
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
246
+ end
247
+ end
248
+ end
249
+ end
250
+ rescue Object => e
251
+ @events.unknown_error self, e, "Listen loop"
252
+ end
253
+ end
254
+
255
+ @events.fire :state, @status
256
+
257
+ graceful_shutdown if @status == :stop || @status == :restart
258
+
259
+ rescue Exception => e
260
+ STDERR.puts "Exception handling servers: #{e.message} (#{e.class})"
261
+ STDERR.puts e.backtrace
262
+ ensure
263
+ begin
264
+ @check.close
265
+ rescue
266
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
267
+ end
268
+
269
+ # Prevent can't modify frozen IOError (RuntimeError)
270
+ begin
271
+ @notify.close
272
+ rescue IOError
273
+ # no biggy
274
+ end
275
+ end
276
+
277
+ @events.fire :state, :done
278
+ end
279
+ # Runs the server.
280
+ #
281
+ # If +background+ is true (the default) then a thread is spun
282
+ # up in the background to handle requests. Otherwise requests
283
+ # are handled synchronously.
284
+ #
285
+ def run(background=true)
286
+ BasicSocket.do_not_reverse_lookup = true
287
+
288
+ @events.fire :state, :booting
289
+
290
+ @status = :run
291
+
292
+ if @mode == :tcp
293
+ return run_lopez_mode(background)
294
+ end
295
+
296
+ queue_requests = @queue_requests
297
+
298
+ @thread_pool = ThreadPool.new(@min_threads,
299
+ @max_threads,
300
+ IOBuffer) do |client, buffer|
301
+
302
+ # Advertise this server into the thread
303
+ Thread.current[ThreadLocalKey] = self
304
+
305
+ process_now = false
306
+
307
+ begin
308
+ if queue_requests
309
+ process_now = client.eagerly_finish
310
+ else
311
+ client.finish
312
+ process_now = true
313
+ end
314
+ rescue MiniSSL::SSLError => e
315
+ ssl_socket = client.io
316
+ addr = ssl_socket.peeraddr.last
317
+ cert = ssl_socket.peercert
318
+
319
+ client.close
320
+
321
+ @events.ssl_error self, addr, cert, e
322
+ rescue HttpParserError => e
323
+ client.write_error(400)
324
+ client.close
325
+
326
+ @events.parse_error self, client.env, e
327
+ rescue ConnectionError, EOFError
328
+ client.close
329
+ else
330
+ if process_now
331
+ process_client client, buffer
332
+ else
333
+ client.set_timeout @first_data_timeout
334
+ @reactor.add client
335
+ end
336
+ end
337
+ end
338
+
339
+ @thread_pool.clean_thread_locals = @options[:clean_thread_locals]
340
+
341
+ if queue_requests
342
+ @reactor = Reactor.new self, @thread_pool
343
+ @reactor.run_in_thread
344
+ end
345
+
346
+ if @reaping_time
347
+ @thread_pool.auto_reap!(@reaping_time)
348
+ end
349
+
350
+ if @auto_trim_time
351
+ @thread_pool.auto_trim!(@auto_trim_time)
352
+ end
353
+
354
+ @events.fire :state, :running
355
+
356
+ if background
357
+ @thread = Thread.new do
358
+ Puma.set_thread_name "server"
359
+ handle_servers
360
+ end
361
+ return @thread
362
+ else
363
+ handle_servers
364
+ end
365
+ end
366
+
367
+ def handle_servers
368
+ begin
369
+ check = @check
370
+ sockets = [check] + @binder.ios
371
+ pool = @thread_pool
372
+ queue_requests = @queue_requests
373
+
374
+ remote_addr_value = nil
375
+ remote_addr_header = nil
376
+
377
+ case @options[:remote_address]
378
+ when :value
379
+ remote_addr_value = @options[:remote_address_value]
380
+ when :header
381
+ remote_addr_header = @options[:remote_address_header]
382
+ end
383
+
384
+ while @status == :run
385
+ begin
386
+ ios = IO.select sockets
387
+ ios.first.each do |sock|
388
+ if sock == check
389
+ break if handle_check
390
+ else
391
+ begin
392
+ if io = sock.accept_nonblock
393
+ client = Client.new io, @binder.env(sock)
394
+ if remote_addr_value
395
+ client.peerip = remote_addr_value
396
+ elsif remote_addr_header
397
+ client.remote_addr_header = remote_addr_header
398
+ end
399
+
400
+ pool << client
401
+ busy_threads = pool.wait_until_not_full
402
+ if busy_threads == 0
403
+ @options[:out_of_band].each(&:call) if @options[:out_of_band]
404
+ end
405
+ end
406
+ rescue SystemCallError
407
+ # nothing
408
+ rescue Errno::ECONNABORTED
409
+ # client closed the socket even before accept
410
+ begin
411
+ io.close
412
+ rescue
413
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
414
+ end
415
+ end
416
+ end
417
+ end
418
+ rescue Object => e
419
+ @events.unknown_error self, e, "Listen loop"
420
+ end
421
+ end
422
+
423
+ @events.fire :state, @status
424
+
425
+ graceful_shutdown if @status == :stop || @status == :restart
426
+ if queue_requests
427
+ @reactor.clear!
428
+ @reactor.shutdown
429
+ end
430
+ rescue Exception => e
431
+ STDERR.puts "Exception handling servers: #{e.message} (#{e.class})"
432
+ STDERR.puts e.backtrace
433
+ ensure
434
+ @check.close
435
+ @notify.close
436
+ end
437
+
438
+ @events.fire :state, :done
439
+ end
440
+
441
+ # :nodoc:
442
+ def handle_check
443
+ cmd = @check.read(1)
444
+
445
+ case cmd
446
+ when STOP_COMMAND
447
+ @status = :stop
448
+ return true
449
+ when HALT_COMMAND
450
+ @status = :halt
451
+ return true
452
+ when RESTART_COMMAND
453
+ @status = :restart
454
+ return true
455
+ end
456
+
457
+ return false
458
+ end
459
+
460
+ # Given a connection on +client+, handle the incoming requests.
461
+ #
462
+ # This method support HTTP Keep-Alive so it may, depending on if the client
463
+ # indicates that it supports keep alive, wait for another request before
464
+ # returning.
465
+ #
466
+ def process_client(client, buffer)
467
+ begin
468
+
469
+ clean_thread_locals = @options[:clean_thread_locals]
470
+ close_socket = true
471
+
472
+ while true
473
+ case handle_request(client, buffer)
474
+ when false
475
+ return
476
+ when :async
477
+ close_socket = false
478
+ return
479
+ when true
480
+ return unless @queue_requests
481
+ buffer.reset
482
+
483
+ ThreadPool.clean_thread_locals if clean_thread_locals
484
+
485
+ unless client.reset(@status == :run)
486
+ close_socket = false
487
+ client.set_timeout @persistent_timeout
488
+ @reactor.add client
489
+ return
490
+ end
491
+ end
492
+ end
493
+
494
+ # The client disconnected while we were reading data
495
+ rescue ConnectionError
496
+ # Swallow them. The ensure tries to close +client+ down
497
+
498
+ # SSL handshake error
499
+ rescue MiniSSL::SSLError => e
500
+ lowlevel_error(e, client.env)
501
+
502
+ ssl_socket = client.io
503
+ addr = ssl_socket.peeraddr.last
504
+ cert = ssl_socket.peercert
505
+
506
+ close_socket = true
507
+
508
+ @events.ssl_error self, addr, cert, e
509
+
510
+ # The client doesn't know HTTP well
511
+ rescue HttpParserError => e
512
+ lowlevel_error(e, client.env)
513
+
514
+ client.write_error(400)
515
+
516
+ @events.parse_error self, client.env, e
517
+
518
+ # Server error
519
+ rescue StandardError => e
520
+ lowlevel_error(e, client.env)
521
+
522
+ client.write_error(500)
523
+
524
+ @events.unknown_error self, e, "Read"
525
+
526
+ ensure
527
+ buffer.reset
528
+
529
+ begin
530
+ client.close if close_socket
531
+ rescue IOError, SystemCallError
532
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
533
+ # Already closed
534
+ rescue StandardError => e
535
+ @events.unknown_error self, e, "Client"
536
+ end
537
+ end
538
+ end
539
+
540
+ # Given a Hash +env+ for the request read from +client+, add
541
+ # and fixup keys to comply with Rack's env guidelines.
542
+ #
543
+ def normalize_env(env, client)
544
+ if host = env[HTTP_HOST]
545
+ if colon = host.index(":")
546
+ env[SERVER_NAME] = host[0, colon]
547
+ env[SERVER_PORT] = host[colon+1, host.bytesize]
548
+ else
549
+ env[SERVER_NAME] = host
550
+ env[SERVER_PORT] = default_server_port(env)
551
+ end
552
+ else
553
+ env[SERVER_NAME] = LOCALHOST
554
+ env[SERVER_PORT] = default_server_port(env)
555
+ end
556
+
557
+ unless env[REQUEST_PATH]
558
+ # it might be a dumbass full host request header
559
+ uri = URI.parse(env[REQUEST_URI])
560
+ env[REQUEST_PATH] = uri.path
561
+
562
+ raise "No REQUEST PATH" unless env[REQUEST_PATH]
563
+
564
+ # A nil env value will cause a LintError (and fatal errors elsewhere),
565
+ # so only set the env value if there actually is a value.
566
+ env[QUERY_STRING] = uri.query if uri.query
567
+ end
568
+
569
+ env[PATH_INFO] = env[REQUEST_PATH]
570
+
571
+ # From http://www.ietf.org/rfc/rfc3875 :
572
+ # "Script authors should be aware that the REMOTE_ADDR and
573
+ # REMOTE_HOST meta-variables (see sections 4.1.8 and 4.1.9)
574
+ # may not identify the ultimate source of the request.
575
+ # They identify the client for the immediate request to the
576
+ # server; that client may be a proxy, gateway, or other
577
+ # intermediary acting on behalf of the actual source client."
578
+ #
579
+
580
+ unless env.key?(REMOTE_ADDR)
581
+ begin
582
+ addr = client.peerip
583
+ rescue Errno::ENOTCONN
584
+ # Client disconnects can result in an inability to get the
585
+ # peeraddr from the socket; default to localhost.
586
+ addr = LOCALHOST_IP
587
+ end
588
+
589
+ # Set unix socket addrs to localhost
590
+ addr = LOCALHOST_IP if addr.empty?
591
+
592
+ env[REMOTE_ADDR] = addr
593
+ end
594
+ end
595
+
596
+ def default_server_port(env)
597
+ if ['on', HTTPS].include?(env[HTTPS_KEY]) || env[HTTP_X_FORWARDED_PROTO].to_s[0...5] == HTTPS || env[HTTP_X_FORWARDED_SCHEME] == HTTPS || env[HTTP_X_FORWARDED_SSL] == "on"
598
+ PORT_443
599
+ else
600
+ PORT_80
601
+ end
602
+ end
603
+
604
+ # Takes the request +req+, invokes the Rack application to construct
605
+ # the response and writes it back to +req.io+.
606
+ #
607
+ # The second parameter +lines+ is a IO-like object unique to this thread.
608
+ # This is normally an instance of Puma::IOBuffer.
609
+ #
610
+ # It'll return +false+ when the connection is closed, this doesn't mean
611
+ # that the response wasn't successful.
612
+ #
613
+ # It'll return +:async+ if the connection remains open but will be handled
614
+ # elsewhere, i.e. the connection has been hijacked by the Rack application.
615
+ #
616
+ # Finally, it'll return +true+ on keep-alive connections.
617
+ def handle_request(req, lines)
618
+ env = req.env
619
+ client = req.io
620
+
621
+ return false if closed_socket?(client)
622
+
623
+ normalize_env env, req
624
+
625
+ env[PUMA_SOCKET] = client
626
+
627
+ if env[HTTPS_KEY] && client.peercert
628
+ env[PUMA_PEERCERT] = client.peercert
629
+ end
630
+
631
+ env[HIJACK_P] = true
632
+ env[HIJACK] = req
633
+
634
+ body = req.body
635
+
636
+ head = env[REQUEST_METHOD] == HEAD
637
+
638
+ env[RACK_INPUT] = body
639
+ env[RACK_URL_SCHEME] = default_server_port(env) == PORT_443 ? HTTPS : HTTP
640
+
641
+ if @early_hints
642
+ env[EARLY_HINTS] = lambda { |headers|
643
+ begin
644
+ fast_write client, "HTTP/1.1 103 Early Hints\r\n".freeze
645
+
646
+ headers.each_pair do |k, vs|
647
+ if vs.respond_to?(:to_s) && !vs.to_s.empty?
648
+ vs.to_s.split(NEWLINE).each do |v|
649
+ fast_write client, "#{k}: #{v}\r\n"
650
+ end
651
+ else
652
+ fast_write client, "#{k}: #{vs}\r\n"
653
+ end
654
+ end
655
+
656
+ fast_write client, "\r\n".freeze
657
+ rescue ConnectionError
658
+ # noop, if we lost the socket we just won't send the early hints
659
+ end
660
+ }
661
+ end
662
+
663
+ # A rack extension. If the app writes #call'ables to this
664
+ # array, we will invoke them when the request is done.
665
+ #
666
+ after_reply = env[RACK_AFTER_REPLY] = []
667
+
668
+ begin
669
+ begin
670
+ status, headers, res_body = @app.call(env)
671
+
672
+ return :async if req.hijacked
673
+
674
+ status = status.to_i
675
+
676
+ if status == -1
677
+ unless headers.empty? and res_body == []
678
+ raise "async response must have empty headers and body"
679
+ end
680
+
681
+ return :async
682
+ end
683
+ rescue ThreadPool::ForceShutdown => e
684
+ @events.log "Detected force shutdown of a thread, returning 503"
685
+ @events.unknown_error self, e, "Rack app"
686
+
687
+ status = 503
688
+ headers = {}
689
+ res_body = ["Request was internally terminated early\n"]
690
+
691
+ rescue Exception => e
692
+ @events.unknown_error self, e, "Rack app", env
693
+
694
+ status, headers, res_body = lowlevel_error(e, env)
695
+ end
696
+
697
+ content_length = nil
698
+ no_body = head
699
+
700
+ if res_body.kind_of? Array and res_body.size == 1
701
+ content_length = res_body[0].bytesize
702
+ end
703
+
704
+ cork_socket client
705
+
706
+ line_ending = LINE_END
707
+ colon = COLON
708
+
709
+ http_11 = if env[HTTP_VERSION] == HTTP_11
710
+ allow_chunked = true
711
+ keep_alive = env.fetch(HTTP_CONNECTION, "").downcase != CLOSE
712
+ include_keepalive_header = false
713
+
714
+ # An optimization. The most common response is 200, so we can
715
+ # reply with the proper 200 status without having to compute
716
+ # the response header.
717
+ #
718
+ if status == 200
719
+ lines << HTTP_11_200
720
+ else
721
+ lines.append "HTTP/1.1 ", status.to_s, " ",
722
+ fetch_status_code(status), line_ending
723
+
724
+ no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
725
+ end
726
+ true
727
+ else
728
+ allow_chunked = false
729
+ keep_alive = env.fetch(HTTP_CONNECTION, "").downcase == KEEP_ALIVE
730
+ include_keepalive_header = keep_alive
731
+
732
+ # Same optimization as above for HTTP/1.1
733
+ #
734
+ if status == 200
735
+ lines << HTTP_10_200
736
+ else
737
+ lines.append "HTTP/1.0 ", status.to_s, " ",
738
+ fetch_status_code(status), line_ending
739
+
740
+ no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
741
+ end
742
+ false
743
+ end
744
+
745
+ response_hijack = nil
746
+
747
+ headers.each do |k, vs|
748
+ case k.downcase
749
+ when CONTENT_LENGTH2
750
+ content_length = vs
751
+ next
752
+ when TRANSFER_ENCODING
753
+ allow_chunked = false
754
+ content_length = nil
755
+ when HIJACK
756
+ response_hijack = vs
757
+ next
758
+ end
759
+
760
+ if vs.respond_to?(:to_s) && !vs.to_s.empty?
761
+ vs.to_s.split(NEWLINE).each do |v|
762
+ lines.append k, colon, v, line_ending
763
+ end
764
+ else
765
+ lines.append k, colon, line_ending
766
+ end
767
+ end
768
+
769
+ if include_keepalive_header
770
+ lines << CONNECTION_KEEP_ALIVE
771
+ elsif http_11 && !keep_alive
772
+ lines << CONNECTION_CLOSE
773
+ end
774
+
775
+ if no_body
776
+ if content_length and status != 204
777
+ lines.append CONTENT_LENGTH_S, content_length.to_s, line_ending
778
+ end
779
+
780
+ lines << line_ending
781
+ fast_write client, lines.to_s
782
+ return keep_alive
783
+ end
784
+
785
+ if content_length
786
+ lines.append CONTENT_LENGTH_S, content_length.to_s, line_ending
787
+ chunked = false
788
+ elsif !response_hijack and allow_chunked
789
+ lines << TRANSFER_ENCODING_CHUNKED
790
+ chunked = true
791
+ end
792
+
793
+ lines << line_ending
794
+
795
+ fast_write client, lines.to_s
796
+
797
+ if response_hijack
798
+ response_hijack.call client
799
+ return :async
800
+ end
801
+
802
+ begin
803
+ res_body.each do |part|
804
+ next if part.bytesize.zero?
805
+ if chunked
806
+ fast_write client, part.bytesize.to_s(16)
807
+ fast_write client, line_ending
808
+ fast_write client, part
809
+ fast_write client, line_ending
810
+ else
811
+ fast_write client, part
812
+ end
813
+
814
+ client.flush
815
+ end
816
+
817
+ if chunked
818
+ fast_write client, CLOSE_CHUNKED
819
+ client.flush
820
+ end
821
+ rescue SystemCallError, IOError
822
+ raise ConnectionError, "Connection error detected during write"
823
+ end
824
+
825
+ ensure
826
+ uncork_socket client
827
+
828
+ body.close
829
+ req.tempfile.unlink if req.tempfile
830
+ res_body.close if res_body.respond_to? :close
831
+
832
+ after_reply.each { |o| o.call }
833
+ end
834
+
835
+ return keep_alive
836
+ end
837
+
838
+ def fetch_status_code(status)
839
+ HTTP_STATUS_CODES.fetch(status) { 'CUSTOM' }
840
+ end
841
+ private :fetch_status_code
842
+
843
+ # Given the request +env+ from +client+ and the partial body +body+
844
+ # plus a potential Content-Length value +cl+, finish reading
845
+ # the body and return it.
846
+ #
847
+ # If the body is larger than MAX_BODY, a Tempfile object is used
848
+ # for the body, otherwise a StringIO is used.
849
+ #
850
+ def read_body(env, client, body, cl)
851
+ content_length = cl.to_i
852
+
853
+ remain = content_length - body.bytesize
854
+
855
+ return StringIO.new(body) if remain <= 0
856
+
857
+ # Use a Tempfile if there is a lot of data left
858
+ if remain > MAX_BODY
859
+ stream = Tempfile.new(Const::PUMA_TMP_BASE)
860
+ stream.binmode
861
+ else
862
+ # The body[0,0] trick is to get an empty string in the same
863
+ # encoding as body.
864
+ stream = StringIO.new body[0,0]
865
+ end
866
+
867
+ stream.write body
868
+
869
+ # Read an odd sized chunk so we can read even sized ones
870
+ # after this
871
+ chunk = client.readpartial(remain % CHUNK_SIZE)
872
+
873
+ # No chunk means a closed socket
874
+ unless chunk
875
+ stream.close
876
+ return nil
877
+ end
878
+
879
+ remain -= stream.write(chunk)
880
+
881
+ # Raed the rest of the chunks
882
+ while remain > 0
883
+ chunk = client.readpartial(CHUNK_SIZE)
884
+ unless chunk
885
+ stream.close
886
+ return nil
887
+ end
888
+
889
+ remain -= stream.write(chunk)
890
+ end
891
+
892
+ stream.rewind
893
+
894
+ return stream
895
+ end
896
+
897
+ # A fallback rack response if +@app+ raises as exception.
898
+ #
899
+ def lowlevel_error(e, env)
900
+ if handler = @options[:lowlevel_error_handler]
901
+ if handler.arity == 1
902
+ return handler.call(e)
903
+ else
904
+ return handler.call(e, env)
905
+ end
906
+ end
907
+
908
+ if @leak_stack_on_error
909
+ [500, {}, ["Puma caught this error: #{e.message} (#{e.class})\n#{e.backtrace.join("\n")}"]]
910
+ else
911
+ [500, {}, ["An unhandled lowlevel error occurred. The application logs may have details.\n"]]
912
+ end
913
+ end
914
+
915
+ # Wait for all outstanding requests to finish.
916
+ #
917
+ def graceful_shutdown
918
+ if @options[:shutdown_debug]
919
+ threads = Thread.list
920
+ total = threads.size
921
+
922
+ pid = Process.pid
923
+
924
+ $stdout.syswrite "#{pid}: === Begin thread backtrace dump ===\n"
925
+
926
+ threads.each_with_index do |t,i|
927
+ $stdout.syswrite "#{pid}: Thread #{i+1}/#{total}: #{t.inspect}\n"
928
+ $stdout.syswrite "#{pid}: #{t.backtrace.join("\n#{pid}: ")}\n\n"
929
+ end
930
+ $stdout.syswrite "#{pid}: === End thread backtrace dump ===\n"
931
+ end
932
+
933
+ if @options[:drain_on_shutdown]
934
+ count = 0
935
+
936
+ while true
937
+ ios = IO.select @binder.ios, nil, nil, 0
938
+ break unless ios
939
+
940
+ ios.first.each do |sock|
941
+ begin
942
+ if io = sock.accept_nonblock
943
+ count += 1
944
+ client = Client.new io, @binder.env(sock)
945
+ @thread_pool << client
946
+ end
947
+ rescue SystemCallError
948
+ end
949
+ end
950
+ end
951
+
952
+ @events.debug "Drained #{count} additional connections."
953
+ end
954
+
955
+ if @status != :restart
956
+ @binder.close
957
+ end
958
+
959
+ if @thread_pool
960
+ if timeout = @options[:force_shutdown_after]
961
+ @thread_pool.shutdown timeout.to_i
962
+ else
963
+ @thread_pool.shutdown
964
+ end
965
+ end
966
+ end
967
+
968
+ def notify_safely(message)
969
+ begin
970
+ @notify << message
971
+ rescue IOError
972
+ # The server, in another thread, is shutting down
973
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
974
+ rescue RuntimeError => e
975
+ # Temporary workaround for https://bugs.ruby-lang.org/issues/13239
976
+ if e.message.include?('IOError')
977
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
978
+ else
979
+ raise e
980
+ end
981
+ end
982
+ end
983
+ private :notify_safely
984
+
985
+ # Stops the acceptor thread and then causes the worker threads to finish
986
+ # off the request queue before finally exiting.
987
+
988
+ def stop(sync=false)
989
+ notify_safely(STOP_COMMAND)
990
+ @thread.join if @thread && sync
991
+ end
992
+
993
+ def halt(sync=false)
994
+ notify_safely(HALT_COMMAND)
995
+ @thread.join if @thread && sync
996
+ end
997
+
998
+ def begin_restart
999
+ notify_safely(RESTART_COMMAND)
1000
+ end
1001
+
1002
+ def fast_write(io, str)
1003
+ n = 0
1004
+ while true
1005
+ begin
1006
+ n = io.syswrite str
1007
+ rescue Errno::EAGAIN, Errno::EWOULDBLOCK
1008
+ if !IO.select(nil, [io], nil, WRITE_TIMEOUT)
1009
+ raise ConnectionError, "Socket timeout writing data"
1010
+ end
1011
+
1012
+ retry
1013
+ rescue Errno::EPIPE, SystemCallError, IOError
1014
+ raise ConnectionError, "Socket timeout writing data"
1015
+ end
1016
+
1017
+ return if n == str.bytesize
1018
+ str = str.byteslice(n..-1)
1019
+ end
1020
+ end
1021
+ private :fast_write
1022
+
1023
+ ThreadLocalKey = :puma_server
1024
+
1025
+ def self.current
1026
+ Thread.current[ThreadLocalKey]
1027
+ end
1028
+
1029
+ def shutting_down?
1030
+ @status == :stop || @status == :restart
1031
+ end
1032
+ end
1033
+ end