‰PNG  IHDR @ @ ªiqÞ pHYs   šœ —tEXtComment require File.expand_path(File.dirname(__FILE__) + "/spec_helper") require 'tmpdir' require 'json' require 'socket' require 'fileutils' require 'net/http' require 'support/apache2_controller' PhusionPassenger.require_passenger_lib 'platform_info' PhusionPassenger.require_passenger_lib 'admin_tools' PhusionPassenger.require_passenger_lib 'admin_tools/instance_registry' WEB_SERVER_DECHUNKS_REQUESTS = false require 'integration_tests/shared/example_webapp_tests' # TODO: test the 'PassengerUserSwitching' and 'PassengerDefaultUser' option. # TODO: test custom page caching directory describe "Apache 2 module" do PORT = ENV.fetch('TEST_PORT_BASE', '64506').to_i before :all do check_hosts_configuration @passenger_temp_dir = Dir.mktmpdir('psg-test-', '/tmp') FileUtils.chmod_R(0777, @passenger_temp_dir) ENV['TMPDIR'] = @passenger_temp_dir ENV['PASSENGER_INSTANCE_REGISTRY_DIR'] = @passenger_temp_dir if File.directory?(PhusionPassenger.install_spec) @log_dir = "#{PhusionPassenger.install_spec}/buildout/testlogs" else @log_dir = "#{@passenger_temp_dir}/testlogs" end @log_file = "#{@log_dir}/apache2.log" FileUtils.mkdir_p(@log_dir) end after :all do begin @apache2.stop if @apache2 FileUtils.cp(Dir["#{@passenger_temp_dir}/passenger-error-*.html"], "#{@log_dir}/") ensure FileUtils.chmod_R(0777, @passenger_temp_dir) FileUtils.rm_rf(@passenger_temp_dir) end end before :each do |example| File.open(@log_file, 'a') do |f| # Make sure that all Apache log output is prepended by the test description # so that we know which messages are associated with which tests. f.puts "\n#### #{Time.now}: #{example.full_description}" @test_log_pos = f.pos end end after :each do |example| log "End of test" if example.exception puts "\t---------------- Begin logs -------------------" File.open(@log_file, 'rb') do |f| f.seek(@test_log_pos) puts f.read.split("\n").map{ |line| "\t#{line}" }.join("\n") end puts "\t---------------- End logs -------------------" puts "\tThe following test failed. The web server logs are printed above." end end def create_apache2_controller @apache2 = Apache2Controller.new(:port => PORT) @apache2.set(:passenger_temp_dir => @passenger_temp_dir, :log_file => @log_file) if CONFIG.has_key?('codesigning_identity') @apache2.set(codesigning_identity: CONFIG['codesigning_identity']) end if Process.uid == 0 @apache2.set( :www_user => CONFIG['normal_user_1'], :www_group => Etc.getgrgid(Etc.getpwnam(CONFIG['normal_user_1']).gid).name ) end end def log(message) File.open(@log_file, 'a') do |f| f.puts "[#{Time.now}] Spec: #{message}" end end describe "a Ruby app running on the root URI" do before :all do create_apache2_controller @server = "http://1.passenger.test:#{@apache2.port}" @stub = RackStub.new('rack') @apache2 << "PassengerMaxPoolSize 1" @apache2.set_vhost("1.passenger.test", "#{@stub.full_app_root}/public") @apache2.start end after :all do @stub.destroy @apache2.stop if @apache2 end before :each do @stub.reset end include_examples "an example web app" end describe "a Ruby app running in a sub-URI" do before :all do create_apache2_controller @server = "http://1.passenger.test:#{@apache2.port}/subapp" @stub = RackStub.new('rack') @apache2 << "PassengerMaxPoolSize 1" @apache2.set_vhost("1.passenger.test", File.expand_path("stub")) do |vhost| vhost << %Q{ Alias /subapp #{@stub.full_app_root}/public PassengerBaseURI /subapp PassengerAppRoot #{@stub.full_app_root} } end @apache2.start end after :all do @stub.destroy @apache2.stop if @apache2 end before :each do @stub.reset end include_examples "an example web app" it "does not interfere with the root website" do @server = "http://1.passenger.test:#{@apache2.port}" get('/').should == "This is the stub directory." end end describe "a Python app running on the root URI" do before :all do create_apache2_controller @server = "http://1.passenger.test:#{@apache2.port}" @stub = PythonStub.new('wsgi') @apache2 << "PassengerMaxPoolSize 1" @apache2.set_vhost("1.passenger.test", "#{@stub.full_app_root}/public") @apache2.start end after :all do @stub.destroy @apache2.stop if @apache2 end before :each do @stub.reset end include_examples "an example web app" end describe "a Python app running in a sub-URI" do before :all do create_apache2_controller @server = "http://1.passenger.test:#{@apache2.port}/subapp" @stub = PythonStub.new('wsgi') @apache2 << "PassengerMaxPoolSize 1" @apache2.set_vhost("1.passenger.test", File.expand_path("stub")) do |vhost| vhost << %Q{ Alias /subapp #{@stub.full_app_root}/public PassengerBaseURI /subapp PassengerAppRoot #{@stub.full_app_root} } end @apache2.start end after :all do @stub.destroy @apache2.stop if @apache2 end before :each do @stub.reset end include_examples "an example web app" it "does not interfere with the root website" do @server = "http://1.passenger.test:#{@apache2.port}" get('/').should == "This is the stub directory." end end describe "a Node.js app running on the root URI" do before :all do create_apache2_controller @server = "http://1.passenger.test:#{@apache2.port}" @stub = NodejsStub.new('node') @apache2 << "PassengerMaxPoolSize 1" @apache2.set_vhost("1.passenger.test", "#{@stub.full_app_root}/public") @apache2.start end after :all do @stub.destroy @apache2.stop if @apache2 end before :each do @stub.reset end include_examples "an example web app" end describe "a Node.js app running in a sub-URI" do before :all do create_apache2_controller @server = "http://1.passenger.test:#{@apache2.port}/subapp" @stub = NodejsStub.new('node') @apache2 << "PassengerMaxPoolSize 1" @apache2.set_vhost("1.passenger.test", File.expand_path("stub")) do |vhost| vhost << %Q{ Alias /subapp #{@stub.full_app_root}/public PassengerBaseURI /subapp PassengerAppRoot #{@stub.full_app_root} } end @apache2.start end after :all do @stub.destroy @apache2.stop if @apache2 end before :each do @stub.reset end include_examples "an example web app" it "does not interfere with the root website" do @server = "http://1.passenger.test:#{@apache2.port}" get('/').should == "This is the stub directory." end end describe "a generic app running on the root URI" do before :all do create_apache2_controller @server = "http://1.passenger.test:#{@apache2.port}" @stub = NodejsStub.new('node') rename_entrypoint_file @apache2 << "PassengerMaxPoolSize 1" @apache2.set_vhost("1.passenger.test", "#{@stub.full_app_root}/public") do |vhost| vhost << "PassengerAppStartCommand 'node boot.js'" end @apache2.start end after :all do @stub.destroy @apache2.stop if @apache2 end before :each do @stub.reset rename_entrypoint_file end def rename_entrypoint_file FileUtils.mv("#{@stub.app_root}/app.js", "#{@stub.app_root}/boot.js") end include_examples "an example web app" end describe "compatibility with other modules" do before :all do create_apache2_controller @apache2 << "PassengerMaxPoolSize 1" @apache2 << "PassengerStatThrottleRate 0" @stub = RackStub.new('rack') @server = "http://1.passenger.test:#{@apache2.port}" @apache2.set_vhost("1.passenger.test", "#{@stub.full_app_root}/public") do |vhost| vhost << "RewriteEngine on" vhost << "RewriteRule ^/rewritten_frontpage$ / [PT,QSA,L]" vhost << "RewriteRule ^/rewritten_env$ /env [PT,QSA,L]" end @apache2.start end after :all do @stub.destroy @apache2.stop if @apache2 end before :each do @stub.reset end it "supports environment variable passing through mod_env" do File.write("#{@stub.app_root}/public/.htaccess", 'SetEnv FOO "Foo Bar!"') File.touch("#{@stub.app_root}/tmp/restart.txt", 2) # Activate ENV changes. get('/system_env').should =~ /^FOO = Foo Bar\!$/ end it "supports mod_rewrite in the virtual host block" do get('/rewritten_frontpage').should == "front page" cgi_envs = get('/rewritten_env?foo=bar+baz') cgi_envs.should include("REQUEST_URI = /env?foo=bar+baz\n") cgi_envs.should include("PATH_INFO = /env\n") end it "supports mod_rewrite in .htaccess" do File.write("#{@stub.app_root}/public/.htaccess", %Q{ RewriteEngine on RewriteRule ^htaccess_frontpage$ / [PT,QSA,L] RewriteRule ^htaccess_env$ env [PT,QSA,L] }) get('/htaccess_frontpage').should == "front page" cgi_envs = get('/htaccess_env?foo=bar+baz') cgi_envs.should include("REQUEST_URI = /env?foo=bar+baz\n") cgi_envs.should include("PATH_INFO = /env\n") end end describe "configuration options" do before :all do create_apache2_controller @apache2 << "PassengerMaxPoolSize 3" @apache2 << "PassengerStatThrottleRate 0" @stub = RackStub.new('rack') @stub_url_root = "http://5.passenger.test:#{@apache2.port}" @apache2.set_vhost('5.passenger.test', "#{@stub.full_app_root}/public") do |vhost| vhost << "PassengerBufferUpload off" vhost << "PassengerFriendlyErrorPages on" vhost << "AllowEncodedSlashes on" end @stub2 = RackStub.new('rack') @stub2_url_root = "http://6.passenger.test:#{@apache2.port}" @apache2.set_vhost('6.passenger.test', "#{@stub2.full_app_root}/public") do |vhost| vhost << "PassengerAppEnv development" vhost << "PassengerSpawnMethod conservative" vhost << "PassengerRestartDir #{@stub2.full_app_root}/public" vhost << "AllowEncodedSlashes off" end @apache2.start end after :all do @stub.destroy @stub2.destroy @apache2.stop if @apache2 end before :each do @stub.reset @stub2.reset end specify "PassengerAppEnv is per-virtual host" do @server = @stub_url_root get('/system_env').should =~ /PASSENGER_APP_ENV = production/ @server = @stub2_url_root get('/system_env').should =~ /PASSENGER_APP_ENV = development/ end it "looks for restart.txt in the directory specified by PassengerRestartDir" do @server = @stub2_url_root startup_file = "#{@stub2.app_root}/config.ru" restart_file = "#{@stub2.app_root}/public/restart.txt" File.write(startup_file, %Q{ require File.expand_path(File.dirname(__FILE__) + "/library") app = lambda do |env| case env['PATH_INFO'] when '/' text_response("hello world") else [404, { "Content-Type" => "text/plain" }, ["Unknown URI"]] end end run app }) now = Time.now File.touch(restart_file, now - 5) get('/').should == "hello world" File.write(startup_file, %Q{ require File.expand_path(File.dirname(__FILE__) + "/library") app = lambda do |env| case env['PATH_INFO'] when '/' text_response("oh hai") else [404, { "Content-Type" => "text/plain" }, ["Unknown URI"]] end end run app }) File.touch(restart_file, now - 10) get('/').should == "oh hai" end describe "PassengerShowVersionInHeader" do before :each do @apache2 << "PassengerShowVersionInHeader " + option @apache2.stop @apache2.start @server = @stub_url_root end context "set to on" do let(:option) { "on" } it "adds version to header" do response = get_response('/') response["X-Powered-By"].should include("Phusion Passenger") response["X-Powered-By"].should include(PhusionPassenger::VERSION_STRING) end end context "set to off" do let(:option) { "off" } it "filters version from header" do response = get_response('/') response["X-Powered-By"].should include("Phusion Passenger") response["X-Powered-By"].should_not include(PhusionPassenger::VERSION_STRING) end end end describe "PassengerAppRoot" do before :each do @server = @stub_url_root File.write("#{@stub.full_app_root}/public/cached.html", "Static cached.html") File.write("#{@stub.full_app_root}/public/dir.html", "Static dir.html") Dir.mkdir("#{@stub.full_app_root}/public/dir") end it "supports page caching on non-index URIs" do get('/cached').should == "Static cached.html" end it "supports page caching on directory index URIs" do get('/dir').should == "Static dir.html" end it "works" do result = get('/parameters?first=one&second=Green+Bananas') result.should =~ %r{First: one\n} result.should =~ %r{Second: Green Bananas\n} end end it "supports encoded slashes in the URL if AllowEncodedSlashes is turned on" do @server = @stub_url_root get('/env/foo%2fbar').should =~ %r{PATH_INFO = /env/foo/bar\n} @server = @stub2_url_root get('/env/foo%2fbar').should =~ %r{404 Not Found} end describe "when handling POST requests with 'chunked' transfer encoding, if PassengerBufferUpload is off" do it "sets Transfer-Encoding to 'chunked' and removes Content-Length" do @uri = URI.parse(@stub_url_root) socket = TCPSocket.new(@uri.host, @uri.port) begin socket.write("POST #{@stub_url_root}/env HTTP/1.1\r\n") socket.write("Host: #{@uri.host}:#{@uri.port}\r\n") socket.write("Transfer-Encoding: chunked\r\n") socket.write("Content-Type: text/plain\r\n") socket.write("Connection: close\r\n") socket.write("\r\n") chunk = "foo=bar!" socket.write("%X\r\n%s\r\n" % [chunk.size, chunk]) socket.write("0\r\n\r\n") socket.flush response = socket.read response.should_not include("CONTENT_LENGTH = ") response.should include("HTTP_TRANSFER_ENCODING = chunked\n") ensure socket.close end end end #################################### end describe "error handling" do before :all do create_apache2_controller @webdir = Dir.mktmpdir('webdir') @apache2.set_vhost('1.passenger.test', @webdir) do |vhost| vhost << "PassengerBaseURI /app-that-crashes-during-startup/public" end @stub = RackStub.new('rack') @stub_url_root = "http://2.passenger.test:#{@apache2.port}" @apache2.set_vhost('2.passenger.test', "#{@stub.full_app_root}/public") @apache2 << "PassengerFriendlyErrorPages on" @apache2.start end after :all do FileUtils.rm_rf(@webdir) @stub.destroy @apache2.stop if @apache2 end before :each do @server = "http://1.passenger.test:#{@apache2.port}" @error_page_signature = /window\.spec = / @stub.reset end it "displays an error page if the application crashes during startup" do RackStub.use('rack', "#{@webdir}/app-that-crashes-during-startup") do |stub| File.prepend(stub.startup_file, "raise 'app crash'") result = get("/app-that-crashes-during-startup/public") result.should =~ @error_page_signature result.should =~ /app crash/ end end it "doesn't display a Ruby spawn error page if PassengerFriendlyErrorPages is off" do RackStub.use('rack', "#{@webdir}/app-that-crashes-during-startup") do |stub| File.write("#{stub.app_root}/public/.htaccess", "PassengerFriendlyErrorPages off") File.prepend(stub.startup_file, "raise 'app crash'") result = get("/app-that-crashes-during-startup/public") result.should_not =~ @error_page_signature result.should_not =~ /app crash/ end end end describe "core" do AdminTools = PhusionPassenger::AdminTools before :all do create_apache2_controller @stub = RackStub.new('rack') @stub_url_root = "http://1.passenger.test:#{@apache2.port}" @apache2 << "PassengerStatThrottleRate 0" @apache2.set_vhost('1.passenger.test', "#{@stub.full_app_root}/public") @apache2.start @server = "http://1.passenger.test:#{@apache2.port}" end after :all do @stub.destroy @apache2.stop if @apache2 end before :each do @stub.reset end def get_newest_instance # Because Apache reloads once during startup, we want to select # the newest Passenger instance. instances = AdminTools::InstanceRegistry.new.list instances.sort! do |a, b| x = a.properties['instance_dir']['created_at_monotonic_usec'] y = b.properties['instance_dir']['created_at_monotonic_usec'] x <=> y end instances.last end it "is restarted if it crashes" do # Make sure that all Apache worker processes have connected to # the Passenger core. 10.times do get('/').should == "front page" sleep 0.1 end # Now kill the Passenger core. Process.kill('SIGKILL', get_newest_instance.core_pid) sleep 0.02 # Give the signal a small amount of time to take effect. # Each worker process should detect that the old # Passenger core has died, and should reconnect. 10.times do get('/').should == "front page" sleep 0.1 end end it "exposes the application pool for passenger-status" do File.touch("#{@stub.app_root}/tmp/restart.txt", 1) # Get rid of all previous app processes. get('/').should == "front page" instance = get_newest_instance # Wait until the server has processed the session close event. sleep 1 request = Net::HTTP::Get.new("/pool.json") request.basic_auth("ro_admin", instance.read_only_admin_password) response = instance.http_request("agents.s/core_api", request) if response.code.to_i / 100 == 2 if RUBY_VERSION >= '2.3' groups = JSON.parse(response.body, symbolize_names: true).to_a.map{|(key,value)| {name: key.to_s, app_root: value.dig(:app_root,0,:value)}} else groups = JSON.parse(response.body, symbolize_names: true).to_a.map{|(key,value)| {name: key.to_s, app_root: value[:app_root][0][:value]}} end else raise response.body end groups.should have(1).item groups.each do |group| group[:name].should == "#{@stub.full_app_root} (production)" # TODO re-enable # processes = group.dig(:processes).map{|p|p.dig(:process)} # processes.should have(1).item # processes[0][:processed].should == "1" end end end ##### Helper methods ##### def start_web_server_if_necessary if !@apache2.running? @apache2.start end end end