Skip to main content

Rack Response

The rack_response plugin allows you to convert an UploadedFile object into a triple consisting of status, headers, and body, suitable for returning as a response in a Rack-based application.

plugin :rack_response

Usage

To convert a Shrine::UploadedFile into a Rack response, simply call #to_rack_response:

status, headers, body = uploaded_file.to_rack_response
status  #=> 200
headers #=>
# {
#   "Content-Length"      => "100",
#   "Content-Type"        => "text/plain",
#   "Content-Disposition" => "inline; filename=\"file.txt\"",
#   "Accept-Ranges"       => "bytes"
# }
body    # object that responds to #each and #close

An example how this can be used in a Rails controller:

class FilesController < ActionController::Base
  def download
    # ...
    set_rack_response record.attachment.to_rack_response
  end

  private

  def set_rack_response((status, headers, body))
    self.status = status
    self.headers.merge!(headers)
    self.response_body = body
  end
end

The #each method on the response body object will stream the uploaded file directly from the storage. It also works with Rack::Sendfile when using FileSystem storage.

Type

The response Content-Type header will default to the value of the mime_type metadata. A custom content type can be provided via the :type option:

response = uploaded_file.to_rack_response(type: "text/plain; charset=utf-8")
response[1]["Content-Type"] #=> "text/plain; charset=utf-8"

Filename

The download filename in the Content-Disposition header will default to the value of the filename metadata. A custom download filename can be provided via the :filename option:

response = uploaded_file.to_rack_response(filename: "my-filename.txt")
response[1]["Content-Disposition"] #=> "inline; filename=\"my-filename.txt\""

Disposition

The default disposition in the "Content-Disposition" header is inline, but it can be changed via the :disposition option:

response = uploaded_file.to_rack_response(disposition: "attachment")
response[1]["Content-Disposition"] #=> "attachment; filename=\"file.txt\""

Range

Partial responses are also supported via the :range option, which accepts a value of the Range request header.

status, headers, body = uploaded_file.to_rack_response(range: env["HTTP_RANGE"])
status                    #=> 206
headers["Content-Length"] #=> "101"
headers["Content-Range"]  #=> "bytes 100-200/1000"
body                      # partial content

Download options

The #to_rack_response method will automatically open the UploadedFile if it hasn't been opened yet. If you want to pass additional download options to the storage, you can explicitly call UploadedFile#open beforehand:

uploaded_file.open(
  sse_customer_algorithm: "AES256",
  sse_customer_key:       "secret_key",
  sse_customer_key_md5:   "secret_key_md5",
)

uploaded_file.to_rack_response