Skip to content

Commit

Permalink
Replace Webrick with custom simple http server (#1030)
Browse files Browse the repository at this point in the history
This commit removes Webrick and replaces it with a (simple) custom
HttpServer.

This is a single-process/singe-thread server that behaves simmilary to
webrick.
  • Loading branch information
dixpac authored Aug 6, 2023
1 parent f879537 commit 14c0a7b
Show file tree
Hide file tree
Showing 9 changed files with 85 additions and 2,616 deletions.
3 changes: 3 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ Layout/ParameterAlignment:
Lint/AmbiguousBlockAssociation:
Enabled: false

Lint/IncompatibleIoSelectWithFiberScheduler:
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

0 comments on commit 14c0a7b

Please sign in to comment.