diff --git a/lib/rackup/handler/webrick.rb b/lib/rackup/handler/webrick.rb index 7a7070f..0847588 100644 --- a/lib/rackup/handler/webrick.rb +++ b/lib/rackup/handler/webrick.rb @@ -16,6 +16,19 @@ module Rackup module Handler class WEBrick < ::WEBrick::HTTPServlet::AbstractServlet + # A WEBrick HTTPServer subclass that invokes the Rack app directly, + # bypassing the mount table and default OPTIONS * handling. + class Server < ::WEBrick::HTTPServer + def initialize(app, config) + super(config) + @handler = Handler::WEBrick.new(self, app) + end + + def service(req, res) + @handler.service(req, res) + end + end + def self.run(app, **options) environment = ENV['RACK_ENV'] || 'development' default_host = environment == 'development' ? 'localhost' : nil @@ -28,8 +41,7 @@ def self.run(app, **options) require 'webrick/https' end - @server = ::WEBrick::HTTPServer.new(options) - @server.mount "/", Rackup::Handler::WEBrick, app + @server = Server.new(app, options) yield @server if block_given? @server.start end @@ -102,11 +114,25 @@ def service(req, res) ) 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] + + # Handle OPTIONS * requests which have no path + if req.unparsed_uri == "*" + env[::Rack::PATH_INFO] = "*" + env[::Rack::REQUEST_PATH] = "*" + + # Ensure SERVER_NAME and SERVER_PORT are set from server config + # (WEBrick allows these to be nil for OPTIONS * requests) + # See https://github.com/ruby/webrick/pull/182 for a proper fix. + env[::Rack::SERVER_NAME] ||= @server[:ServerName] || @server[:BindAddress] || "localhost" + env[::Rack::SERVER_PORT] ||= (@server[:Port] || 80).to_s + else + unless env[::Rack::PATH_INFO] == "" + # Strip the script name prefix from the path to get path info + script_name_length = env[::Rack::SCRIPT_NAME].length + env[::Rack::PATH_INFO] = req.request_uri.path[script_name_length..-1] || "" + end + env[::Rack::REQUEST_PATH] ||= env[::Rack::SCRIPT_NAME] + env[::Rack::PATH_INFO] end - env[::Rack::REQUEST_PATH] ||= [env[::Rack::SCRIPT_NAME], env[::Rack::PATH_INFO]].join status, headers, body = @app.call(env) begin diff --git a/test/spec_webrick.rb b/test/spec_webrick.rb index 792de36..ad0ff62 100644 --- a/test/spec_webrick.rb +++ b/test/spec_webrick.rb @@ -220,6 +220,50 @@ def is_running? end end + it "handle OPTIONS * requests through the Rack app" do + app = proc do |env| + if env["REQUEST_METHOD"] == "OPTIONS" && env["PATH_INFO"] == "*" + [200, {"allow" => "GET,HEAD,POST,PUT,DELETE,OPTIONS"}, [""]] + else + [404, {"content-type" => "text/plain"}, ["Not Found"]] + end + end + + server = Rackup::Handler::WEBrick::Server.new( + app, + Host: @host, + Port: 9203, + Logger: WEBrick::Log.new(nil, WEBrick::BasicLog::WARN), + AccessLog: [] + ) + + thread = Thread.new { server.start } + + # Wait for server to start + seconds = 10 + wait_time = 0.1 + until server.status == :Running || seconds <= 0 + seconds -= wait_time + sleep wait_time + end + + begin + TCPSocket.open(@host, 9203) do |socket| + socket.write "OPTIONS * HTTP/1.1\r\n" + socket.write "Host: #{@host}\r\n" + socket.write "Connection: close\r\n\r\n" + + response = socket.read + response.must_match(/HTTP\/1.1 200/) + # The Rack app should set the Allow header, not WEBrick's default + response.must_match(/Allow: GET,HEAD,POST,PUT,DELETE,OPTIONS/i) + end + ensure + server.shutdown + thread.join + end + end + after do @status_thread.join @server.shutdown