puma-simon 3.7.1

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