FEATURE: avatar proxy happens in background
This ensures that even if it is slow to download avatars site will continue to work Also simplifies hijack pattern
This commit is contained in:
+26
-70
@@ -5,58 +5,10 @@
|
||||
# free up a unicorn worker while the remote IO is happening
|
||||
module Hijack
|
||||
|
||||
class FakeResponse
|
||||
attr_reader :headers
|
||||
def initialize
|
||||
@headers = {}
|
||||
end
|
||||
end
|
||||
|
||||
class Binder
|
||||
attr_reader :content_type, :body, :status, :response
|
||||
|
||||
def initialize
|
||||
@content_type = 'text/plain'
|
||||
@status = 500
|
||||
@body = ""
|
||||
@response = FakeResponse.new
|
||||
end
|
||||
|
||||
def immutable_for(duration)
|
||||
response.headers['Cache-Control'] = "max-age=#{duration}, public, immutable"
|
||||
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
|
||||
|
||||
if opts.key?(:content_type)
|
||||
@content_type = opts[:content_type]
|
||||
end
|
||||
|
||||
if opts.key?(:plain)
|
||||
@content_type = 'text/plain; charset=utf-8'
|
||||
@body = opts[:plain].to_s
|
||||
end
|
||||
|
||||
if opts.key?(:json)
|
||||
@content_type = 'application/json; charset=utf-8'
|
||||
@body = opts[:json]
|
||||
unless String === @body
|
||||
@body = @body.to_json
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def hijack(&blk)
|
||||
controller_class = self.class
|
||||
request = self.request
|
||||
|
||||
if hijack = request.env['rack.hijack']
|
||||
io = hijack.call
|
||||
|
||||
@@ -67,26 +19,41 @@ module Hijack
|
||||
# before doing any work
|
||||
io.write "HTTP/1.1 "
|
||||
|
||||
binder = Binder.new
|
||||
# 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
|
||||
instance.request = request
|
||||
|
||||
begin
|
||||
binder.instance_eval(&blk)
|
||||
instance.instance_eval(&blk)
|
||||
rescue => e
|
||||
Rails.logger.warn("Failed to process hijacked response correctly #{e}")
|
||||
end
|
||||
|
||||
headers = binder.response.headers
|
||||
headers['Content-Length'] = binder.body.bytesize
|
||||
headers['Content-Type'] = binder.content_type
|
||||
unless instance.response_body
|
||||
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"
|
||||
headers['Connection'] = "close"
|
||||
|
||||
io.write "#{binder.status} OK\r\n"
|
||||
status_string = Rack::Utils::HTTP_STATUS_CODES[instance.status.to_i] || "Unknown"
|
||||
io.write "#{instance.status} #{status_string}\r\n"
|
||||
|
||||
headers.each do |name, val|
|
||||
io.write "#{name}: #{val}\r\n"
|
||||
end
|
||||
|
||||
io.write "\r\n"
|
||||
io.write binder.body
|
||||
io.write body
|
||||
io.close
|
||||
rescue Errno::EPIPE, IOError
|
||||
# happens if client terminated before we responded, ignore
|
||||
@@ -95,18 +62,7 @@ module Hijack
|
||||
# not leaked out, we use 418 ... I am a teapot to denote that we are hijacked
|
||||
render plain: "", status: 418
|
||||
else
|
||||
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,
|
||||
status: binder.status
|
||||
)
|
||||
blk.call
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user