Skip to content

Instantly share code, notes, and snippets.

@kirs

kirs/ractor_rack.rb

Last active Jan 8, 2021
Embed
What would you like to do?
require 'webrick'
# How much of this is solved by frozen string literal?
Ractor.make_shareable(WEBrick::Config::HTTP)
Ractor.make_shareable(WEBrick::LF)
Ractor.make_shareable(WEBrick::CRLF)
Ractor.make_shareable(WEBrick::HTTPRequest::BODY_CONTAINABLE_METHODS)
Ractor.make_shareable(WEBrick::HTTPStatus::StatusMessage)
# To pick up changes from https://github.com/ruby/ruby/pull/4007
Object.send(:remove_const, :URI)
require '/Users/kir/src/github.com/ruby/ruby/lib/uri.rb'
require 'rack'
# Make it frozen
Ractor.make_shareable(Rack::VERSION)
# https://github.com/ruby/ruby/pull/4008
Ractor.make_shareable(Time::RFC2822_DAY_NAME)
Ractor.make_shareable(Time::RFC2822_MONTH_NAME)
require 'uri'
Ractor.make_shareable(URI::RFC3986_PARSER)
Ractor.make_shareable(URI::DEFAULT_PARSER)
require 'stringio'
pipe = Ractor.new do
loop do
Ractor.yield(Ractor.recv, move: true)
end
end
# has to be explicitly required from the main thread:
# https://bugs.ruby-lang.org/issues/17477
require 'pp'
def env_from_request(req)
env = req.meta_vars
env.delete_if { |k, v| v.nil? }
rack_input = StringIO.new(req.body.to_s)
rack_input.set_encoding(Encoding::BINARY)
env.update(
Rack::RACK_VERSION => Rack::VERSION,
Rack::RACK_INPUT => rack_input,
Rack::RACK_ERRORS => $stderr,
Rack::RACK_MULTITHREAD => true,
Rack::RACK_MULTIPROCESS => false,
Rack::RACK_RUNONCE => false,
Rack::RACK_URL_SCHEME => ["yes", "on", "1"].include?(env[Rack::HTTPS]) ? "https" : "http"
)
env[Rack::QUERY_STRING] ||= ""
unless env[Rack::PATH_INFO] == ""
path, n = req.request_uri.path, env[Rack::SCRIPT_NAME].length
env[Rack::PATH_INFO] = path[n, path.length - n]
end
env[Rack::REQUEST_PATH] ||= [env[Rack::SCRIPT_NAME], env[Rack::PATH_INFO]].join
env
end
CPU_COUNT = 4
workers = CPU_COUNT.times.map do
Ractor.new(pipe) do |pipe|
app = lambda do |e|
[200, {'Content-Type' => 'text/html'}, ['hello world']]
end
loop do
s = pipe.take
req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP.merge(RequestTimeout: nil))
req.parse(s)
env = env_from_request(req)
status, headers, body = app.call(env)
resp = WEBrick::HTTPResponse.new(WEBrick::Config::HTTP)
begin
resp.status = status.to_i
io_lambda = nil
headers.each { |k, vs|
if k.downcase == "set-cookie"
resp.cookies.concat vs.split("\n")
else
# Since WEBrick won't accept repeated headers,
# merge the values per RFC 1945 section 4.2.
resp[k] = vs.split("\n").join(", ")
end
}
body.each { |part|
resp.body << part
}
ensure
body.close if body.respond_to? :close
end
pp env
resp.send_response(s)
end
end
end
listener = Ractor.new(pipe) do |pipe|
server = TCPServer.new(8080)
loop do
conn, _ = server.accept
pipe.send(conn, move: true)
end
end
loop do
Ractor.select(listener, *workers)
# if the line above returned, one of the workers or the listener has crashed
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment