puma 4.3.0-java

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