diff --git a/lib/web_console.rb b/lib/web_console.rb index 0ba155a4..04c8cae9 100644 --- a/lib/web_console.rb +++ b/lib/web_console.rb @@ -10,7 +10,7 @@ module WebConsole autoload :Evaluator autoload :ExceptionMapper autoload :Session - autoload :Response + autoload :Injector autoload :Request autoload :WhinyRequest autoload :Whitelist diff --git a/lib/web_console/injector.rb b/lib/web_console/injector.rb new file mode 100644 index 00000000..03773995 --- /dev/null +++ b/lib/web_console/injector.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module WebConsole + # Injects content into a Rack body. + class Injector + def initialize(body) + @body = "".dup + + body.each { |part| @body << part } + body.close if body.respond_to?(:close) + end + + def inject(content) + if position = @body.rindex("") + [ @body.insert(position, content) ] + else + [ @body << content ] + end + end + end +end diff --git a/lib/web_console/middleware.rb b/lib/web_console/middleware.rb index e8f26746..ece9cdd0 100644 --- a/lib/web_console/middleware.rb +++ b/lib/web_console/middleware.rb @@ -30,16 +30,14 @@ def call(env) status, headers, body = call_app(env) if (session = Session.from(Thread.current)) && acceptable_content_type?(headers) - response = Response.new(body, status, headers) - template = Template.new(env, session) + headers["X-Web-Console-Session-Id"] = session.id + headers["X-Web-Console-Mount-Point"] = mount_point - response.headers["X-Web-Console-Session-Id"] = session.id - response.headers["X-Web-Console-Mount-Point"] = mount_point - response.write(template.render("index")) - response.finish - else - [ status, headers, body ] + template = Template.new(env, session) + body = Injector.new(body).inject(template.render("index")) end + + [ status, headers, body ] end rescue => e WebConsole.logger.error("\n#{e.class}: #{e}\n\tfrom #{e.backtrace.join("\n\tfrom ")}") @@ -64,7 +62,7 @@ def json_response(opts = {}) headers = { "Content-Type" => "application/json; charset = utf-8" } body = yield.to_json - Rack::Response.new(body, status, headers).finish + [ status, headers, [ body ] ] end def json_response_with_session(id, request, opts = {}) diff --git a/lib/web_console/response.rb b/lib/web_console/response.rb deleted file mode 100644 index 350b6d54..00000000 --- a/lib/web_console/response.rb +++ /dev/null @@ -1,27 +0,0 @@ -# frozen_string_literal: true - -module WebConsole - # A response object that writes content before the closing tag, if - # possible. - # - # The object quacks like Rack::Response. - class Response < Struct.new(:body, :status, :headers) - def write(content) - raw_body = Array(body).first.to_s - - # We're done with the original body object, so make sure to close it to comply with the Rack SPEC - body.close if body.respond_to?(:close) - - self.body = - if position = raw_body.rindex("") - raw_body.dup.insert(position, content) - else - raw_body.dup << content - end - end - - def finish - Rack::Response.new(body, status, headers).finish - end - end -end diff --git a/test/web_console/injector_test.rb b/test/web_console/injector_test.rb new file mode 100644 index 00000000..434fae9c --- /dev/null +++ b/test/web_console/injector_test.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require "test_helper" + +module WebConsole + class InjectorTest < ActiveSupport::TestCase + test "closes body if closable" do + closed = false + + body = [ "foo" ] + body.define_singleton_method(:close) { closed = true } + + assert_equal [ "foobar" ], Injector.new(body).inject("bar") + assert closed + end + + test "support fancy bodies like Rack::BodyProxy" do + closed = false + body = Rack::BodyProxy.new([ "foo" ]) { closed = true } + + assert_equal [ "foobar" ], Injector.new(body).inject("bar") + assert closed + end + + test "support fancy bodies like ActionDispatch::Response::RackBody" do + body = ActionDispatch::Response.create(200, {}, [ "foo" ]).to_a.last + + assert_equal [ "foobar" ], Injector.new(body).inject("bar") + end + end +end