Support configuring the puma server to use SSL

This commit is contained in:
Thomas Walpole 2018-05-01 13:36:42 -07:00
parent bbec20f91d
commit 170bc99d68
6 changed files with 123 additions and 8 deletions

View File

@ -61,10 +61,14 @@ module Capybara
attr_reader :app, :port, :host
def initialize(app, port = Capybara.server_port, host = Capybara.server_host, server_errors = Capybara.server_errors)
def initialize(app, *deprecated_options, port: Capybara.server_port, host: Capybara.server_host, reportable_errors: Capybara.server_errors)
warn "Positional arguments, other than the application, to Server#new are deprecated, please use keyword arguments" unless deprecated_options.empty?
@app = app
@server_thread = nil # suppress warnings
@host, @port, @server_errors = host, port, server_errors
@host = deprecated_options[1] || host
@reportable_errors = deprecated_options[2] || reportable_errors
@using_ssl = false
@port = deprecated_options[0] || port
@port ||= Capybara::Server.ports[port_key]
@port ||= find_available_port(host)
end
@ -77,10 +81,23 @@ module Capybara
middleware.error
end
def using_ssl?
@using_ssl
end
def responsive?
return false if @server_thread && @server_thread.join(0)
res = Net::HTTP.start(host, port) { |http| http.get('/__identify__') }
begin
res = if !@using_ssl
http_connect
else
https_connect
end
rescue EOFError, Net::ReadTimeout
res = https_connect
@using_ssl = true
end
if res.is_a?(Net::HTTPSuccess) or res.is_a?(Net::HTTPRedirection)
return res.body == app.object_id.to_s
@ -121,8 +138,16 @@ module Capybara
private
def http_connect
Net::HTTP.start(host, port, read_timeout: 2) { |http| http.get('/__identify__') }
end
def https_connect
Net::HTTP.start(host, port, use_ssl: true, verify_mode: OpenSSL::SSL::VERIFY_NONE) { |http| http.get('/__identify__') }
end
def middleware
@middleware ||= Middleware.new(app, @server_errors)
@middleware ||= Middleware.new(app, @reportable_errors)
end
def port_key

View File

@ -84,7 +84,7 @@ module Capybara
yield config
end
@server = if config.run_server and @app and driver.needs_server?
Capybara::Server.new(@app, config.server_port, config.server_host, config.server_errors).boot
Capybara::Server.new(@app, port: config.server_port, host: config.server_host, reportable_errors: config.server_errors).boot
else
nil
end
@ -249,7 +249,7 @@ module Capybara
visit_uri = ::Addressable::URI.parse(visit_uri.to_s)
uri_base = if @server
::Addressable::URI.parse(config.app_host || "http://#{@server.host}:#{@server.port}")
::Addressable::URI.parse(config.app_host || "http#{'s' if @server.using_ssl?}://#{@server.host}:#{@server.port}")
else
config.app_host && ::Addressable::URI.parse(config.app_host)
end

View File

@ -43,7 +43,7 @@ RSpec.describe Capybara do
it "should default to a proc that calls run_default_server" do
mock_app = Object.new
allow(Capybara).to receive(:run_default_server)
allow(Capybara).to receive(:run_default_server).and_return(true)
Capybara.server.call(mock_app, 8000)
expect(Capybara).to have_received(:run_default_server).with(mock_app, 8000)
end

25
spec/fixtures/certificate.pem vendored Normal file
View File

@ -0,0 +1,25 @@
-----BEGIN CERTIFICATE-----
MIIEITCCAwmgAwIBAgIJALROkwd1gZHQMA0GCSqGSIb3DQEBBQUAMGgxCzAJBgNV
BAYTAlVTMQswCQYDVQQIEwJDQTERMA8GA1UEChMIQ2FweWJhcmExFjAUBgNVBAMT
DWNhcHliYXJhLnRlc3QxITAfBgkqhkiG9w0BCQEWEnR3YWxwb2xlQGdtYWlsLmNv
bTAeFw0xODA1MDEyMDI5NDVaFw0yODA0MjgyMDI5NDVaMGgxCzAJBgNVBAYTAlVT
MQswCQYDVQQIEwJDQTERMA8GA1UEChMIQ2FweWJhcmExFjAUBgNVBAMTDWNhcHli
YXJhLnRlc3QxITAfBgkqhkiG9w0BCQEWEnR3YWxwb2xlQGdtYWlsLmNvbTCCASIw
DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL7icqMv9uApxRXlcIQ3hSEfmULk
7CLT1aUAjEmTiqy8TkFqOeSuA3elnbVBhOW+emrb1uUyje20LvEOHbqYYw90ezlV
jqNWUapawqjxb+q7KVNpA5uDCtOHIC/1Z1kxQ+yZP/8St4SvnLGUMsUhu0h+ywJa
iGZ2xy8wdfjABUiUImRESdNkT7Xs0wxQGY0/FZ4K5Sec4kroOuvdxKKhyf5Js5xS
NRv2tXSMWlKHbYEvYzsVtFgv/4GjqN4wVvbfJmsS8thcyrSYSMN7eE0R6dcbJaLM
P/GTiw669zPJP164K84QoabLClgwyG+7mqjm7jutq9qXipwyrGsf/WR5fkUCAwEA
AaOBzTCByjAdBgNVHQ4EFgQU4ut9c0oB2OGMlN/nvn0Y7twBzJowgZoGA1UdIwSB
kjCBj4AU4ut9c0oB2OGMlN/nvn0Y7twBzJqhbKRqMGgxCzAJBgNVBAYTAlVTMQsw
CQYDVQQIEwJDQTERMA8GA1UEChMIQ2FweWJhcmExFjAUBgNVBAMTDWNhcHliYXJh
LnRlc3QxITAfBgkqhkiG9w0BCQEWEnR3YWxwb2xlQGdtYWlsLmNvbYIJALROkwd1
gZHQMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAANtqdDrdehBt20s
IVOsVzOp+tJI8pn6G62WJcZWGsvLLfx6Y6eJSP24r5NW4v39cqCgE76J2znzKxeo
VIShAJ1SLodR0GwzFGsl3TVKVoce6gBWZNXgkZyJTNqKGWeme8CH1EG3TqYlcybt
EXWLQ6wKndD+uHJIfqUy7HfaUv+TghJGLkv8QYqcAWILQko4xqZ1NULDQ2uhjxei
oaQlK9teHlVaH87dONesAg3f5rh9j6nvzGiC/T8ycdEhn/DxJMxRS2+mVZqKkk7U
60+e9Foc6qTW8UaMShIFYMspoRnr9baBxzxh9V9LZznwqVTf3vuEIck2l2j2KL/R
SOBenCM=
-----END CERTIFICATE-----

27
spec/fixtures/key.pem vendored Normal file
View File

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAvuJyoy/24CnFFeVwhDeFIR+ZQuTsItPVpQCMSZOKrLxOQWo5
5K4Dd6WdtUGE5b56atvW5TKN7bQu8Q4duphjD3R7OVWOo1ZRqlrCqPFv6rspU2kD
m4MK04cgL/VnWTFD7Jk//xK3hK+csZQyxSG7SH7LAlqIZnbHLzB1+MAFSJQiZERJ
02RPtezTDFAZjT8VngrlJ5ziSug6693EoqHJ/kmznFI1G/a1dIxaUodtgS9jOxW0
WC//gaOo3jBW9t8maxLy2FzKtJhIw3t4TRHp1xslosw/8ZOLDrr3M8k/XrgrzhCh
pssKWDDIb7uaqObuO62r2peKnDKsax/9ZHl+RQIDAQABAoIBABhZepYmgC+IJIPu
iLPVAT6AcWR/H0AyFYa+0yZvk7kFLFZb3pa1O+v/TGbavMEx0xvef0Mtd71ixrop
OtGarshB65Ycu91KHZDFkx9J7STcSyFAvB0SUkc5bXmwrEZMaoW75tX65T4fyLU+
WlubOfC9e9gJBG1NqYrze5kHpaTkR8pBaSxECns5y2HLaMvs1t1kK7snqtGHFRAb
deAUl0ONENaO6PF797yM3bsIEgnljdKvaVP3kYq/eAzH4jyw+qG/xq0bZ+nalJzM
IL7xRSR/Yak7WZ3QUh99t3dng/z3De4yvhBLq/mc2mIVvt7aERPo/aDG4SrmQ9Jw
f1GUT+ECgYEA+i96w2zPHkq0djuuhNaa/R0vybD9hVCdB7enQ8skBzQAQyhp9X9W
by38VAbFRVIMrzdfbvwSFrE3Nd9BvyLWl16G+MVu+hq0jhM9p44a62vXvfgQmfFh
iXyGkzCTufeLEzOFnP8S+ezacodK1hbQTjllUndn6NUhXcbfozTPfx8CgYEAw1Il
xqfU7NkVX6UP/FvTx9aBKqwrD8YOPn9nj26Q45zVbmHJZZIvywlsMDCiJ6JJawEa
GSkiZTDpypEvS/2UtLEY0zfbSbQGkBTnagHIFAuVh0AD1EP+kCcnrxA5Vjr8v0WE
B0HBfMgoZxa8rmHMpPhrlCCo1ectRgu+dgzEKhsCgYAQ17lwBpc69tSHUSVClCAD
AkABWAT5QKARsO91xOs8AOgznTjk6hmrinD+RyZosEliUlv+YMHm/S82VT1b3MCN
mDOF8+SwubOGDQ2NhieRycTQaS7U7kcetl9o8VBAqMWYGVPZaeKhKKzcIPeMyiRj
38FOd/Nq3U5NveG4XwnJCQKBgGrzmnfTAsbGf+uliMFYzviIPqZNLC8w9i/Gt8BU
fMYF5ODSbuNNTxpQiItCtigZtzX+nnnUil76j6o6IbnsmvbuWneeCFetWkKfD7B+
VT6UsUYkCXS73rK0nghATAUpu6hIumj22qonN+hrDNo390UGOnIcCBdIxQOr/pjJ
mMitAoGAB3tFzlzZHpCE38IqdHL7CKIML9cYJSOm07nFIEUAKN2bUHGsvMH/tMS1
I3hyeHxMmYCrOLn7xLqiOVgRjbYS9JiQVdDojqeSvvnLCtY9/lHWi50pqbPfwyfK
Og6QoFyxjBEsoSilc6dai58VTO6ufhoJXbXX9PcIl7le4dVPnzE=
-----END RSA PRIVATE KEY-----

View File

@ -51,7 +51,7 @@ RSpec.describe Capybara::Server do
it "should use given port" do
@app = proc { |_env| [200, {}, ["Hello Server!"]] }
@server = Capybara::Server.new(@app, 22790).boot
@server = Capybara::Server.new(@app, port: 22790).boot
@res = Net::HTTP.start(@server.host, 22790) { |http| http.get('/') }
expect(@res.body).to include('Hello Server')
@ -73,6 +73,28 @@ RSpec.describe Capybara::Server do
expect(@res2.body).to include('Hello Second Server')
end
it "should support SSL" do
begin
key = File.join(Dir.pwd, "spec", "fixtures", "key.pem")
cert = File.join(Dir.pwd, "spec", "fixtures", "certificate.pem")
Capybara.server = :puma, { Host: "ssl://#{Capybara.server_host}?key=#{key}&cert=#{cert}" }
app = proc { |_env| [200, {}, ['Hello SSL Server!']] }
server = Capybara::Server.new(app).boot
expect do
Net::HTTP.start(server.host, server.port) { |http| http.get('/__idntify__') }
end.to raise_error(EOFError)
res = Net::HTTP.start(server.host, server.port, use_ssl: true, verify_mode: OpenSSL::SSL::VERIFY_NONE) do |https|
https.get('/')
end
expect(res.body).to include('Hello SSL Server!')
ensure
Capybara.server = :default
end
end
context "When Capybara.reuse_server is true" do
before do
@old_reuse_server = Capybara.reuse_server
@ -193,6 +215,22 @@ RSpec.describe Capybara::Server do
expect(server.responsive?).to eq false
end
[EOFError, Net::ReadTimeout].each do |err|
it "should attempt an HTTPS connection if HTTP connection returns #{err}" do
app = -> { [200, {}, ['Hello, world']] }
ordered_errors = [Errno::ECONNREFUSED, err]
# allow(Net::HTTP).to receive(:start).with(anything, anything, hash_excluding(:use_ssl)).and_yield { raise Errno::ECONNREFUSED }
allow(Net::HTTP).to receive(:start).with(anything, anything, hash_excluding(:use_ssl)) do
raise ordered_errors.shift
end
response = Net::HTTPSuccess.allocate
allow(response).to receive(:body).and_return app.object_id.to_s
allow(Net::HTTP).to receive(:start).with(anything, anything, hash_including(use_ssl: true)).and_return(response).once
Capybara::Server.new(app).boot
expect(Net::HTTP).to have_received(:start).exactly(3).times
end
end
def start_request(server, wait_time)
# Start request, but don't wait for it to finish
socket = TCPSocket.new(server.host, server.port)