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
|
|
|
|
|
|
|
class FakeResponse
|
|
|
|
attr_reader :headers
|
|
|
|
def initialize
|
|
|
|
@headers = {}
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-11-23 23:31:23 -05:00
|
|
|
class Binder
|
2017-11-26 22:50:57 -05:00
|
|
|
attr_reader :content_type, :body, :status, :response
|
2017-11-23 23:31:23 -05:00
|
|
|
|
|
|
|
def initialize
|
|
|
|
@content_type = 'text/plain'
|
|
|
|
@status = 500
|
|
|
|
@body = ""
|
2017-11-26 22:50:57 -05:00
|
|
|
@response = FakeResponse.new
|
|
|
|
end
|
|
|
|
|
|
|
|
def immutable_for(duration)
|
|
|
|
response.headers['Cache-Control'] = "max-age=#{duration}, public, immutable"
|
2017-11-23 23:31:23 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def render(opts)
|
|
|
|
if opts[:status]
|
|
|
|
@status = opts[:status].to_i
|
|
|
|
else
|
|
|
|
@status = 200
|
|
|
|
end
|
|
|
|
|
|
|
|
if opts.key?(:body)
|
|
|
|
@body = opts[:body].to_s
|
|
|
|
end
|
|
|
|
|
2017-11-26 22:50:57 -05:00
|
|
|
if opts.key?(:content_type)
|
|
|
|
@content_type = opts[:content_type]
|
|
|
|
end
|
|
|
|
|
2017-11-23 23:31:23 -05:00
|
|
|
if opts.key?(:plain)
|
2017-11-26 20:43:18 -05:00
|
|
|
@content_type = 'text/plain; charset=utf-8'
|
2017-11-23 23:31:23 -05:00
|
|
|
@body = opts[:plain].to_s
|
|
|
|
end
|
2017-11-26 20:43:18 -05:00
|
|
|
|
|
|
|
if opts.key?(:json)
|
|
|
|
@content_type = 'application/json; charset=utf-8'
|
|
|
|
@body = opts[:json]
|
|
|
|
unless String === @body
|
|
|
|
@body = @body.to_json
|
|
|
|
end
|
|
|
|
end
|
2017-11-23 23:31:23 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def hijack(&blk)
|
|
|
|
if hijack = request.env['rack.hijack']
|
|
|
|
io = hijack.call
|
|
|
|
|
|
|
|
Scheduler::Defer.later("hijack work") do
|
|
|
|
|
|
|
|
begin
|
|
|
|
# do this first to confirm we have a working connection
|
|
|
|
# before doing any work
|
|
|
|
io.write "HTTP/1.1 "
|
|
|
|
|
|
|
|
binder = Binder.new
|
|
|
|
begin
|
|
|
|
binder.instance_eval(&blk)
|
|
|
|
rescue => e
|
|
|
|
Rails.logger.warn("Failed to process hijacked response correctly #{e}")
|
|
|
|
end
|
|
|
|
|
2017-11-26 22:50:57 -05:00
|
|
|
headers = binder.response.headers
|
|
|
|
headers['Content-Length'] = binder.body.bytesize
|
|
|
|
headers['Content-Type'] = binder.content_type
|
|
|
|
headers['Connection'] = "close"
|
|
|
|
|
2017-11-23 23:31:23 -05:00
|
|
|
io.write "#{binder.status} OK\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"
|
|
|
|
io.write binder.body
|
|
|
|
io.close
|
|
|
|
rescue Errno::EPIPE, IOError
|
|
|
|
# happens if client terminated before we responded, ignore
|
|
|
|
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-26 22:50:57 -05:00
|
|
|
binder = Binder.new
|
|
|
|
binder.instance_eval(&blk)
|
|
|
|
|
|
|
|
binder.response.headers.each do |name, val|
|
|
|
|
response.headers[name] = val
|
|
|
|
end
|
|
|
|
|
|
|
|
render(
|
|
|
|
body: binder.body,
|
|
|
|
content_type: binder.content_type,
|
2017-11-26 23:07:13 -05:00
|
|
|
status: binder.status
|
2017-11-26 22:50:57 -05:00
|
|
|
)
|
2017-11-23 23:31:23 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|