Skip to content

Commit

Permalink
Update to Puma 5.6.1
Browse files Browse the repository at this point in the history
No need to care about the port gotchas with foreman now that we can use
ssl_bind in the Puma config: puma/puma#2764

Close #309
  • Loading branch information
dentarg committed Jan 27, 2022
1 parent 01d0fed commit c9ed81c
Show file tree
Hide file tree
Showing 10 changed files with 114 additions and 97 deletions.
2 changes: 1 addition & 1 deletion .foreman
@@ -1,2 +1,2 @@
formation: ssl=1
formation: web=1
port: 8080
2 changes: 1 addition & 1 deletion Gemfile.lock
Expand Up @@ -64,7 +64,7 @@ GEM
coderay (~> 1.1)
method_source (~> 1.0)
public_suffix (4.0.6)
puma (5.5.2)
puma (5.6.1)
nio4r (~> 2.0)
racc (1.5.2)
rack (2.2.3)
Expand Down
1 change: 0 additions & 1 deletion Procfile
@@ -1,2 +1 @@
web: bundle exec puma -C config/puma.rb
ssl: bundle exec puma -b tcp://localhost:$(($PORT-100)) -b ssl://localhost:$(($PORT-100-1000))
37 changes: 3 additions & 34 deletions config.ru
Expand Up @@ -6,42 +6,11 @@ require 'rack/ssl-enforcer'

use Raven::Rack

def production?
ENV.fetch('RACK_ENV') == 'production'
end

def development?
ENV.fetch('RACK_ENV') == 'development'
end

def test?
ENV.fetch('RACK_ENV') == 'test'
end

def load_localhost_ssl?
return true if development?

ENV.key?("LOAD_LOCALHOST_SSL") # to force load when "simulating" production
end

def redirect_to_https?
return true if production?

%w(1 true).include?(ENV["REDIRECT_TO_HTTPS"])
end

require_relative 'config/app'

# SSL/TLS in development on port $PORT-1000 (port $PORT will redirect there)
# https://github.com/socketry/localhost
require 'localhost' if load_localhost_ssl?

if redirect_to_https?
options = if development?
# Subtract 100 because of foreman offset bug:
# https://github.com/ddollar/foreman/issues/714
# https://github.com/ddollar/foreman/issues/418
{ https_port: ENV.fetch('PORT').to_i - 100 - 1000, hsts: false }
if App.redirect_to_https?
options = if App.development?
{ https_port: App.ssl_port, hsts: false }
else
{ hsts: { subdomains: false } }
end
Expand Down
6 changes: 4 additions & 2 deletions config/app.rb
Expand Up @@ -3,7 +3,9 @@
require 'sinatra/base'
require 'sequel'

if ENV.fetch('RACK_ENV') == 'development'
require_relative '../lib/app'

if App.development?
ENV['SESSION_SECRET'] ||= 'secret'

$stdout.sync = true
Expand All @@ -12,7 +14,7 @@

DB = Sequel.connect(ENV.fetch('DATABASE_URL', 'postgres://localhost/wikimum'))

if ENV.fetch('RACK_ENV') == 'development'
if App.development?
require 'logger'
DB.logger = Logger.new($stdout)

Expand Down
12 changes: 10 additions & 2 deletions config/puma.rb
@@ -1,13 +1,21 @@
# frozen_string_literal: true

require_relative "../lib/app"

workers Integer(ENV['PUMA_WORKERS'] || 3)
threads Integer(ENV['MIN_THREADS'] || 1), Integer(ENV['MAX_THREADS'] || 16)

preload_app!

rackup DefaultRackup
port ENV['PORT'] || 3000
environment ENV['RACK_ENV'] || 'development'
port App.port
environment App.env

if App.localhost_ssl?
require "localhost" # https://github.com/socketry/localhost
# SSL/TLS in development on port $PORT-1000 (port $PORT will redirect there)
ssl_bind "0.0.0.0", App.ssl_port
end

lowlevel_error_handler do |ex, env|
Raven.capture_exception(
Expand Down
42 changes: 42 additions & 0 deletions lib/app.rb
@@ -0,0 +1,42 @@
# frozen_string_literal: true

class App
class << self
def port
ENV.fetch("PORT", 3000).to_i
end

def ssl_port
port - 1000
end

def env
ENV.fetch("RACK_ENV", "development")
end

def production?
env == "production"
end

def development?
env == "development"
end

def test?
env == "test"
end

def localhost_ssl?
return true if development?

# to force use when "simulating" production
ENV.key?("LOAD_LOCALHOST_SSL")
end

def redirect_to_https?
return true if production?

%w(1 true).include?(ENV["REDIRECT_TO_HTTPS"])
end
end
end
109 changes: 53 additions & 56 deletions test/integration/app_boot_test.rb
Expand Up @@ -26,6 +26,21 @@ def random_free_port(host: "127.0.0.1")
server&.close
end

def get_https_response(host: "localhost", port:)
response = nil
ssl_opts = {
use_ssl: true,
verify_mode: OpenSSL::SSL::VERIFY_NONE,
}


Net::HTTP.start(host, port, ssl_opts) do |https|
response = https.get("/")
end

response
end

def setup
WebMock.disable_net_connect!(allow_localhost: true)
end
Expand All @@ -45,81 +60,63 @@ def test_app_boot
end

def test_app_development_boot
port = random_free_port.to_s
port = random_free_port
options = {
timeout: 5,
wait_for: /Worker.+booted/,
env: {
PORT: port,
RACK_ENV: "development",
REDIRECT_TO_HTTPS: true,
},
}

ClimateControl.modify(RACK_ENV: "development", PORT: port) do
command = command_from_procfile(worker: "ssl")

WaitForIt.new(command, options) do |spawn|
puts spawn.log.read if ENV.key?("DEBUG")
actual_port = (port.to_i - 100) # because we workaround foreman issue in Procfile

http_res = Net::HTTP.get_response(URI("http://localhost:#{actual_port}"))
assert_equal ["https://localhost:#{actual_port - 1000}/"], http_res.get_fields("location")
assert_equal "301", http_res.code

https_res = nil
https_port = actual_port - 1000
ssl_opts = {
use_ssl: true,
verify_mode: OpenSSL::SSL::VERIFY_NONE,
}
Net::HTTP.start("localhost", https_port, ssl_opts) do |http|
https_res = http.get("/")
end

assert_equal "200", https_res.code
refute https_res.key?("strict-transport-security")
end
WaitForIt.new(command_from_procfile, options) do |spawn|
puts spawn.log.read if ENV.key?("DEBUG")

http_res = Net::HTTP.get_response(URI("http://localhost:#{port}"))
https_port = port - 1000
https_res = get_https_response(port: https_port)

# Test HTTP respone
assert_equal ["https://localhost:#{https_port}/"], http_res.get_fields("location")
assert_equal "301", http_res.code

# Test HTTPS respone
assert_equal "200", https_res.code
refute https_res.key?("strict-transport-security")
end
end

def test_app_production_boot
port = random_free_port.to_s
port = random_free_port
options = {
timeout: 5,
wait_for: /Worker.+booted/,
env: {
LOAD_LOCALHOST_SSL: true,
PORT: port,
RACK_ENV: "production",
LOAD_LOCALHOST_SSL: true, # Tell Puma to bind TLS/SSL port, to simulate production
},
}

ClimateControl.modify(RACK_ENV: "production", PORT: port) do
# Need Puma to bind TLS/SSL port to simulate production
command = command_from_procfile(worker: "ssl")

# command can not be prefixed with ENV variables due to use of "exec" in wait_for_it
# https://github.com/zombocom/wait_for_it/blob/v0.2.1/lib/wait_for_it.rb#L179-L180
WaitForIt.new(command, options) do |spawn|
puts spawn.log.read if ENV.key?("DEBUG")
actual_port = (port.to_i - 100) # because we workaround foreman issue in Procfile

# Test HTTP respone, should redirect without any port in production
http_res = Net::HTTP.get_response(URI("http://localhost:#{actual_port}"))
assert_equal "301", http_res.code
assert_equal ["https://localhost/"], http_res.get_fields("location")

# Test HTTPS respone
https_res = nil
https_port = actual_port - 1000
ssl_opts = {
use_ssl: true,
verify_mode: OpenSSL::SSL::VERIFY_NONE,
}
Net::HTTP.start("localhost", https_port, ssl_opts) do |http|
https_res = http.get("/")
end

assert_equal "200", https_res.code
assert_equal ["max-age=31536000"], https_res.get_fields("strict-transport-security")
end
# Good to know:
# command can not be prefixed with ENV variables due to use of "exec" in wait_for_it
# https://github.com/zombocom/wait_for_it/blob/v0.2.1/lib/wait_for_it.rb#L179-L180
WaitForIt.new(command_from_procfile, options) do |spawn|
puts spawn.log.read if ENV.key?("DEBUG")

http_res = Net::HTTP.get_response(URI("http://localhost:#{port}"))
https_port = port - 1000
https_res = get_https_response(port: https_port)

# Test HTTP respone, should redirect without any port in production
assert_equal "301", http_res.code
assert_equal ["https://localhost/"], http_res.get_fields("location")

# Test HTTPS respone
assert_equal "200", https_res.code
assert_equal ["max-age=31536000"], https_res.get_fields("strict-transport-security")
end
end
end
Binary file removed vendor/cache/puma-5.5.2.gem
Binary file not shown.
Binary file added vendor/cache/puma-5.6.1.gem
Binary file not shown.

0 comments on commit c9ed81c

Please sign in to comment.