Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace Webrick with custom simple http server #1030

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ Layout/ParameterAlignment:
Lint/AmbiguousBlockAssociation:
Enabled: false

Lint/IncompatibleIoSelectWithFiberScheduler:
Copy link
Contributor Author

@dixpac dixpac Aug 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fiber-aware scheduling, instead of ths
ready_sockets, = IO.select([@server])
this:
@server.wait_readable

But afaik Ruby 2.x.x, doesn't have fiber-aware scheduling or wait_readable, so I've disabled this linter.

Enabled: false

Metrics:
Enabled: false

Expand Down
2 changes: 0 additions & 2 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ PATH
fugit (>= 1.1)
railties (>= 6.0.0)
thor (>= 0.14.1)
webrick (>= 1.3)

GEM
remote: https://rubygems.org/
Expand Down Expand Up @@ -454,7 +453,6 @@ GEM
unparser (0.6.8)
diff-lcs (~> 1.3)
parser (>= 3.2.0)
webrick (1.8.1)
websocket (1.2.9)
websocket-driver (0.7.5)
websocket-extensions (>= 0.1.0)
Expand Down
1 change: 0 additions & 1 deletion good_job.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ Gem::Specification.new do |spec|
spec.add_dependency "fugit", ">= 1.1"
spec.add_dependency "railties", ">= 6.0.0"
spec.add_dependency "thor", ">= 0.14.1"
spec.add_dependency "webrick", ">= 1.3"

spec.add_development_dependency "benchmark-ips"
spec.add_development_dependency "capybara"
Expand Down
1 change: 1 addition & 0 deletions lib/good_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
require "good_job/multi_scheduler"
require "good_job/notifier"
require "good_job/poller"
require "good_job/http_server"
require "good_job/probe_server"
require "good_job/scheduler"
require "good_job/shared_executor"
Expand Down
75 changes: 75 additions & 0 deletions lib/good_job/http_server.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# frozen_string_literal: true

module GoodJob
class HttpServer
SOCKET_READ_TIMEOUT = 5 # in seconds

def initialize(app, options = {})
@app = app
@port = options[:port]
@logger = options[:logger]

@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
end

def running?
@running.true?
end

private

def start_server
@server = TCPServer.new('0.0.0.0', @port)
rescue StandardError => e
@logger.error "Failed to start server: #{e}"
@running.make_false
end

def handle_connections
while @running.true?
begin
ready_sockets, = IO.select([@server], nil, nil, SOCKET_READ_TIMEOUT)
return unless ready_sockets

client = @server.accept_nonblock(exception: false)
request = client.gets

status, headers, body = @app.call(parse_request(request))
respond(client, status, headers, body)

client.close
rescue IO::WaitReadable, Errno::EINTR
retry
end
end
end

def parse_request(request)
method, full_path = request.split
path, query = full_path.split('?')
{ 'REQUEST_METHOD' => method, 'PATH_INFO' => path, 'QUERY_STRING' => query || '' }
end

def respond(client, status, headers, body)
client.write "HTTP/1.1 #{status}\r\n"
headers.each { |key, value| client.write "#{key}: #{value}\r\n" }
client.write "\r\n"
body.each { |part| client.write part.to_s }
end
end
end
12 changes: 4 additions & 8 deletions lib/good_job/probe_server.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

module GoodJob
class ProbeServer
RACK_SERVER = 'webrick'

def self.task_observer(time, output, thread_error) # rubocop:disable Lint/UnusedMethodArgument
return if thread_error.is_a? Concurrent::CancelledOperationError

Expand All @@ -15,20 +13,18 @@ def initialize(port:)
end

def start
@handler = Rack::Handler.get(RACK_SERVER)
@future = Concurrent::Future.new(args: [@handler, @port, GoodJob.logger]) do |thr_handler, thr_port, thr_logger|
thr_handler.run(self, Port: thr_port, Host: '0.0.0.0', Logger: thr_logger, AccessLog: [])
end
@handler = HttpServer.new(self, port: @port, logger: GoodJob.logger)
@future = Concurrent::Future.new { @handler.run }
@future.add_observer(self.class, :task_observer)
@future.execute
end

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

def stop
@handler&.shutdown
@handler&.stop
@future&.value # wait for Future to exit
end

Expand Down
Loading