Skip to content

Commit

Permalink
Add WEBrick WEBrick handler
Browse files Browse the repository at this point in the history
  • Loading branch information
jklina committed Jan 2, 2024
1 parent d5520aa commit ac2885c
Show file tree
Hide file tree
Showing 7 changed files with 148 additions and 56 deletions.
3 changes: 2 additions & 1 deletion lib/good_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@
require "good_job/probe_server"
require "good_job/probe_server/middleware/catchall"
require "good_job/probe_server/middleware/healthcheck"
require "good_job/http_server"
require "good_job/http_server_handler"
require "good_job/webrick_handler"
require "good_job/scheduler"
require "good_job/shared_executor"
require "good_job/systemd_service"
Expand Down
5 changes: 4 additions & 1 deletion lib/good_job/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ def exit_on_failure?
type: :numeric,
banner: 'PORT',
desc: "Port for http health check (env var: GOOD_JOB_PROBE_PORT, default: nil)"
method_option :probe_handler,
type: :string,
desc: "Use 'webrick' to use WEBrick to handle probe server requests which is Rack compliant, otherwise default server that is not Rack compliant is used. (env var: GOOD_JOB_PROBE_HANDLER, default: nil)"
method_option :queue_select_limit,
type: :numeric,
banner: 'COUNT',
Expand All @@ -105,7 +108,7 @@ def start
systemd.start

if configuration.probe_port
probe_server = GoodJob::ProbeServer.new(app: configuration.probe_server_app, port: configuration.probe_port)
probe_server = GoodJob::ProbeServer.new(app: configuration.probe_server_app, port: configuration.probe_port, handler: configuration.probe_handler)
probe_server.start
end

Expand Down
22 changes: 11 additions & 11 deletions lib/good_job/http_server.rb → lib/good_job/http_server_handler.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

module GoodJob
class HttpServer
class HttpServerHandler
SOCKET_READ_TIMEOUT = 5 # in seconds

def initialize(app, options = {})
Expand All @@ -12,16 +12,6 @@ def initialize(app, options = {})
@running = Concurrent::AtomicBoolean.new(false)
end

def run
@running.make_true
start_server
handle_connections if @running.true?
rescue StandardError => e
@logger.error "Server encountered an error: #{e}"
ensure
stop
end

def stop
@running.make_false
@server&.close
Expand All @@ -37,6 +27,16 @@ def build_future

private

def run
@running.make_true
start_server
handle_connections if @running.true?
rescue StandardError => e
@logger.error "Server encountered an error: #{e}"
ensure
stop
end

def start_server
@server = TCPServer.new('0.0.0.0', @port)
rescue StandardError => e
Expand Down
13 changes: 8 additions & 5 deletions lib/good_job/probe_server.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@ def self.task_observer(time, output, thread_error) # rubocop:disable Lint/Unused
end

def initialize(port:, handler: nil, app: default_probe_server)
@port = port
@app = app
@handler = handler || default_handler
@handler = build_handler(port: port, handler: handler, app: app)
end

def start
Expand All @@ -38,8 +36,13 @@ def default_probe_server
end
end

def default_handler
HttpServer.new(@app, port: @port, logger: GoodJob.logger)
def build_handler(port:, handler:, app:)
if handler == 'webrick'
require 'webrick'
WebrickHandler.new(app, port: port, logger: GoodJob.logger)
else
HttpServerHandler.new(app, port: port, logger: GoodJob.logger)
end
end
end
end
26 changes: 26 additions & 0 deletions lib/good_job/webrick_handler.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# frozen_string_literal: true

module GoodJob
class WebrickHandler
def initialize(app, options = {})
@app = app
@port = options[:port]
@logger = options[:logger]
@handler = Rack::Handler.get('webrick')
end

def stop
@handler&.shutdown
end

def running?
@handler&.instance_variable_get(:@server)&.status == :Running
end

def build_future
Concurrent::Future.new(args: [@handler, @port, GoodJob.logger]) do |thr_handler, thr_port, thr_logger|
thr_handler.run(@app, Port: thr_port, Host: '0.0.0.0', Logger: thr_logger, AccessLog: [])
end
end
end
end
2 changes: 1 addition & 1 deletion spec/lib/good_job/cli_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@
probe_port: 3838,
options: {},
daemonize?: false,
shutdown_timeout: 100,
shutdown_timeout: 100
)
allow(GoodJob).to receive_messages(configuration: configuration_mock)
cli = described_class.new([], [], {})
Expand Down
133 changes: 96 additions & 37 deletions spec/lib/good_job/probe_server_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,58 +7,117 @@
let(:port) { 3434 }

describe '#start' do
context 'with the default healthcheck app' do
it 'starts a http server that binds to all interfaces and returns healthcheck responses' do
probe_server = described_class.new(port: port)
probe_server.start
wait_until(max: 1) { expect(probe_server).to be_running }
context "with default HTTP server" do
context 'with the default healthcheck app' do
it 'starts a http server that binds to all interfaces and returns healthcheck responses' do
probe_server = described_class.new(port: port)
probe_server.start
wait_until(max: 1) { expect(probe_server).to be_running }

ip_addresses = Socket.ip_address_list.select(&:ipv4?).map(&:ip_address)
expect(ip_addresses.size).to be >= 2
expect(ip_addresses).to include("127.0.0.1")
ip_addresses = Socket.ip_address_list.select(&:ipv4?).map(&:ip_address)
expect(ip_addresses.size).to be >= 2
expect(ip_addresses).to include("127.0.0.1")

aggregate_failures do
ip_addresses.each do |ip_address|
response = Net::HTTP.get(ip_address, "/", port)
expect(response).to eq("OK")
aggregate_failures do
ip_addresses.each do |ip_address|
response = Net::HTTP.get(ip_address, "/", port)
expect(response).to eq("OK")

response = Net::HTTP.get(ip_address, "/status", port)
expect(response).to eq("OK")
response = Net::HTTP.get(ip_address, "/status", port)
expect(response).to eq("OK")

response = Net::HTTP.get(ip_address, "/status/started", port)
expect(response).to eq("Not started")
response = Net::HTTP.get(ip_address, "/status/started", port)
expect(response).to eq("Not started")

response = Net::HTTP.get(ip_address, "/status/connected", port)
expect(response).to eq("Not connected")
response = Net::HTTP.get(ip_address, "/status/connected", port)
expect(response).to eq("Not connected")

response = Net::HTTP.get(ip_address, "/unimplemented_url", port)
expect(response).to eq("Not found")
response = Net::HTTP.get(ip_address, "/unimplemented_url", port)
expect(response).to eq("Not found")
end
end

probe_server.stop
end
end

context 'with a provided app' do
it 'starts a http server that binds to all interfaces and uses the supplied app' do
app = proc { [200, { "Content-Type" => "text/plain" }, ["Hello World"]] }
probe_server = described_class.new(app: app, port: port)
probe_server.start
wait_until(max: 1) { expect(probe_server).to be_running }

ip_addresses = Socket.ip_address_list.select(&:ipv4?).map(&:ip_address)
expect(ip_addresses.size).to be >= 2
expect(ip_addresses).to include("127.0.0.1")

aggregate_failures do
ip_addresses.each do |ip_address|
response = Net::HTTP.get(ip_address, "/", port)
expect(response).to eq("Hello World")
end
end

probe_server.stop
probe_server.stop
end
end
end

context 'with a provided app' do
it 'starts a http server that binds to all interfaces and uses the supplied app' do
app = proc { [200, { "Content-Type" => "text/plain" }, ["Hello World"]] }
probe_server = described_class.new(app: app, port: port)
probe_server.start
wait_until(max: 1) { expect(probe_server).to be_running }

ip_addresses = Socket.ip_address_list.select(&:ipv4?).map(&:ip_address)
expect(ip_addresses.size).to be >= 2
expect(ip_addresses).to include("127.0.0.1")

aggregate_failures do
ip_addresses.each do |ip_address|
response = Net::HTTP.get(ip_address, "/", port)
expect(response).to eq("Hello World")
context "with WEBrick" do
context 'with the default healthcheck app' do
it 'starts a http server that binds to all interfaces and returns healthcheck responses' do
probe_server = described_class.new(port: port, handler: "webrick")
probe_server.start
wait_until(max: 1) { expect(probe_server).to be_running }

ip_addresses = Socket.ip_address_list.select(&:ipv4?).map(&:ip_address)
expect(ip_addresses.size).to be >= 2
expect(ip_addresses).to include("127.0.0.1")

aggregate_failures do
ip_addresses.each do |ip_address|
response = Net::HTTP.get(ip_address, "/", port)
expect(response).to eq("OK")

response = Net::HTTP.get(ip_address, "/status", port)
expect(response).to eq("OK")

response = Net::HTTP.get(ip_address, "/status/started", port)
expect(response).to eq("Not started")

response = Net::HTTP.get(ip_address, "/status/connected", port)
expect(response).to eq("Not connected")

response = Net::HTTP.get(ip_address, "/unimplemented_url", port)
expect(response).to eq("Not found")
end
end

probe_server.stop
end
end

probe_server.stop
context 'with a provided app' do
it 'starts a http server that binds to all interfaces and uses the supplied app' do
app = proc { [200, { "Content-Type" => "text/plain" }, ["Hello World"]] }
probe_server = described_class.new(app: app, port: port)
probe_server.start
wait_until(max: 1) { expect(probe_server).to be_running }

ip_addresses = Socket.ip_address_list.select(&:ipv4?).map(&:ip_address)
expect(ip_addresses.size).to be >= 2
expect(ip_addresses).to include("127.0.0.1")

aggregate_failures do
ip_addresses.each do |ip_address|
response = Net::HTTP.get(ip_address, "/", port)
expect(response).to eq("Hello World")
end
end

probe_server.stop
end
end
end
end
Expand Down

0 comments on commit ac2885c

Please sign in to comment.