Upload Endpoint
The upload_endpoint
plugin provides a Rack endpoint which
accepts file uploads and forwards them to specified storage. On the client side
it's recommended to use Uppy for asynchronous uploads.
plugin :upload_endpoint
Setup
The plugin adds a Shrine.upload_endpoint
method which, given a storage
identifier, returns a Rack application that accepts multipart POST requests,
and uploads received files to the specified storage. You can run this Rack
application inside your app:
# config/routes.rb (Rails) Rails.application.routes.draw do # ... mount ImageUploader.upload_endpoint(:cache) => "/images/upload"end
Asynchronous upload is typically meant to replace the caching phase in the
default synchronous workflow, so we want the uploads to go to temporary
(:cache
) storage.
The above will create a POST /images/upload
endpoint, which uploads the file
received in the file
param using ImageUploader
, and returns a JSON
representation of the uploaded file.
# POST /images/upload { "id": "43kewit94.jpg", "storage": "cache", "metadata": { "size": 384393, "filename": "nature.jpg", "mime_type": "image/jpeg" }}
This JSON string can now be assigned to an attachment attribute instead of a raw file. In a form it can be written to a hidden attachment field, and then it can be assigned as the attachment.
Calling from a controller
If you want to run additional code around the upload (such as authentication),
mounting the upload endpoint in your router might be limiting. You can instead
create a custom controller action and handle upload requests there using
Shrine.upload_response
:
# config/routes.rb (Rails) Rails.application.routes.draw do # ... post "/images/upload", to: "uploads#image"end
# app/controllers/uploads_controller.rb (Rails) # ... we can perform authentication here ... set_rack_response ImageUploader.upload_response(:cache, request.env) end private ) self.status = status self.headers.merge!(headers) self.response_body = body endend
Limiting filesize
It's good practice to limit the accepted filesize of uploaded files. You can do
that with the :max_size
option:
plugin :upload_endpoint, max_size: 20*1024*1024 # 20 MB
If the uploaded file is larger than the specified value, a 413 Payload Too Large
response will be returned.
Uploader options
You can pass additional uploader options via :upload_context
:
plugin :upload_endpoint, upload_context: -> (request) do {location: "my-location" }end
Note that the uploader will not receive :record
and :name
values, as the
upload happens independently of a database record.
Upload
You can also customize the upload itself via the :upload
option:
plugin :upload_endpoint, upload: -> (io, options, request) do Shrine.upload(io, :cache, **options)end
URL
You can have the endpoint include the uploaded file URL in the response body
by specifying the :url
option:
plugin :upload_endpoint, url: true# or plugin :upload_endpoint, url: {public: true }# or plugin :upload_endpoint, url: -> (uploaded_file, request) { uploaded_file.url(**options)}
In this case the response body will be:
{ "data": {"id": "...", "storage": "...", "metadata": {...} }, "url": "https://example.com/path/to/file"}
Response
The response returned by the endpoint can be customized via the
:rack_response
option:
plugin :upload_endpoint, rack_response: -> (uploaded_file, request) do body = {data: uploaded_file.data, url: uploaded_file.url }.to_json [201, {"Content-Type" => "application/json" }, [body]]end
Ad-hoc options
You can override any of the options above when creating the endpoint/response:
Shrine.upload_endpoint(:cache, max_size: 20*1024*1024)# or Shrine.upload_response(:cache, request.env, max_size: 20*1024*1024)
Checksum
If you want the upload endpoint to verify the integrity of the uploaded file,
you can include the Content-MD5
header in the request filled with the
base64-encoded MD5 hash of the file that was calculated prior to the upload,
and the endpoint will automatically use it to verify the uploaded data.
If the checksums don't match, a 460 Checksum Mismatch
response is returned.