2017-11-23 23:31:23 -05:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
|
|
# This module allows us to hijack a request and send it to the client in the deferred job queue
|
|
|
|
# For cases where we are making remote calls like onebox or proxying files and so on this helps
|
|
|
|
# free up a unicorn worker while the remote IO is happening
|
|
|
|
module Hijack
|
2017-11-26 22:50:57 -05:00
|
|
|
|
2017-11-23 23:31:23 -05:00
|
|
|
def hijack(&blk)
|
2017-11-27 01:43:24 -05:00
|
|
|
controller_class = self.class
|
2017-11-27 17:28:40 -05:00
|
|
|
|
2017-11-23 23:31:23 -05:00
|
|
|
if hijack = request.env['rack.hijack']
|
|
|
|
|
2017-11-28 00:47:20 -05:00
|
|
|
request.env['discourse.request_tracker.skip'] = true
|
|
|
|
request_tracker = request.env['discourse.request_tracker']
|
|
|
|
|
2017-11-27 17:28:40 -05:00
|
|
|
# in prd the env object is re-used
|
|
|
|
# make a copy of all strings
|
|
|
|
env_copy = {}
|
|
|
|
request.env.each do |k, v|
|
2017-11-28 02:21:45 -05:00
|
|
|
env_copy[k] = v if String === v || Hash === v
|
2017-11-27 17:28:40 -05:00
|
|
|
end
|
2017-11-28 00:47:20 -05:00
|
|
|
# we require that for request initialization
|
|
|
|
env_copy["rack.input"] = StringIO.new
|
2017-11-27 17:28:40 -05:00
|
|
|
|
|
|
|
# params is generated per request so we can simply reuse it
|
|
|
|
params_copy = params
|
|
|
|
|
2017-11-28 00:47:20 -05:00
|
|
|
env_copy["action_dispatch.request.parameters"] = params_copy
|
|
|
|
|
|
|
|
request_copy = ActionDispatch::Request.new(env_copy)
|
|
|
|
|
|
|
|
transfer_timings = MethodProfiler.transfer if defined? MethodProfiler
|
|
|
|
|
2017-11-28 02:21:45 -05:00
|
|
|
io = hijack.call
|
|
|
|
|
|
|
|
Scheduler::Defer.later("hijack #{params["controller"]} #{params["action"]}") do
|
2017-11-23 23:31:23 -05:00
|
|
|
|
2017-11-28 00:47:20 -05:00
|
|
|
MethodProfiler.start(transfer_timings) if defined? MethodProfiler
|
|
|
|
|
2017-11-23 23:31:23 -05:00
|
|
|
begin
|
|
|
|
# do this first to confirm we have a working connection
|
|
|
|
# before doing any work
|
|
|
|
io.write "HTTP/1.1 "
|
|
|
|
|
2017-11-27 01:43:24 -05:00
|
|
|
# this trick avoids double render, also avoids any litter that the controller hooks
|
|
|
|
# place on the response
|
|
|
|
instance = controller_class.new
|
|
|
|
response = ActionDispatch::Response.new
|
|
|
|
instance.response = response
|
2017-11-27 18:59:53 -05:00
|
|
|
|
2017-11-27 17:28:40 -05:00
|
|
|
instance.request = request_copy
|
|
|
|
instance.params = params_copy
|
2017-11-27 01:43:24 -05:00
|
|
|
|
2017-11-23 23:31:23 -05:00
|
|
|
begin
|
2017-11-27 01:43:24 -05:00
|
|
|
instance.instance_eval(&blk)
|
2017-11-23 23:31:23 -05:00
|
|
|
rescue => e
|
|
|
|
Rails.logger.warn("Failed to process hijacked response correctly #{e}")
|
|
|
|
end
|
|
|
|
|
2017-11-27 18:59:53 -05:00
|
|
|
unless instance.response_body || response.committed?
|
2017-11-27 01:43:24 -05:00
|
|
|
instance.status = 500
|
|
|
|
end
|
|
|
|
|
|
|
|
response.commit!
|
|
|
|
|
|
|
|
body = response.body
|
|
|
|
|
|
|
|
headers = response.headers
|
|
|
|
headers['Content-Length'] = body.bytesize
|
|
|
|
headers['Content-Type'] = response.content_type || "text/plain"
|
2017-11-26 22:50:57 -05:00
|
|
|
headers['Connection'] = "close"
|
|
|
|
|
2017-11-27 01:43:24 -05:00
|
|
|
status_string = Rack::Utils::HTTP_STATUS_CODES[instance.status.to_i] || "Unknown"
|
|
|
|
io.write "#{instance.status} #{status_string}\r\n"
|
2017-11-26 22:50:57 -05:00
|
|
|
|
|
|
|
headers.each do |name, val|
|
|
|
|
io.write "#{name}: #{val}\r\n"
|
|
|
|
end
|
|
|
|
|
2017-11-23 23:31:23 -05:00
|
|
|
io.write "\r\n"
|
2017-11-27 01:43:24 -05:00
|
|
|
io.write body
|
2017-11-23 23:31:23 -05:00
|
|
|
io.close
|
|
|
|
rescue Errno::EPIPE, IOError
|
|
|
|
# happens if client terminated before we responded, ignore
|
2017-11-28 00:47:20 -05:00
|
|
|
ensure
|
|
|
|
if request_tracker
|
|
|
|
status = instance.status rescue 500
|
|
|
|
timings = MethodProfiler.stop if defined? MethodProfiler
|
|
|
|
request_tracker.log_request_info(env_copy, [status, headers || {}, []], timings)
|
|
|
|
end
|
2017-11-23 23:31:23 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
# not leaked out, we use 418 ... I am a teapot to denote that we are hijacked
|
|
|
|
render plain: "", status: 418
|
|
|
|
else
|
2017-11-27 01:43:24 -05:00
|
|
|
blk.call
|
2017-11-23 23:31:23 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|