piesync-puma 3.12.6

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