Packages

An Elixir Plug parser for image direct upload.

Current section

Files

Jump to
image_plug_parser lib image_plug_parser.ex
Raw

lib/image_plug_parser.ex

defmodule ImagePlugParser do
@moduledoc """
Parses image in PUT request body.
## Usage
Image parser must be used only in controller
defmodule Server.Web.FilesController do
use Server.Web, :controller
plug Plug.Parsers,
parsers: [ImagePlugParser],
subtypes: ["jpeg", "png"]
def upload(conn, %{"file" => %{filename: filename, path: path, content_type: mimetype}}) do
path
|> FilesManager.upload(filename, mimetype)
|> response(conn)
end
end
and router must set wildcard path with name `filename`
defmodule Server.Web.Router do
use Server.Web, :router
pipeline :api do
plug :accepts, ["json"]
end
scope "/api", Server.Web do
pipe_through :api
put "/files/:page_id/*filename", FilesController, :upload
end
end
## Options
* `:subtypes` - list of supported image content types, eg. jpeg or png
All options supported by `Plug.Conn.read_body/2` are also supported here.
They are repeated here for convenience:
* `:length` - sets the maximum number of bytes to read from the request,
defaults to 8_000_000 bytes
* `:read_length` - sets the amount of bytes to read at one time from the
underlying socket to fill the chunk, defaults to 1_000_000 bytes
* `:read_timeout` - sets the timeout for each socket read, defaults to
15_000ms
So by default, `Plug.Parsers` will read 1_000_000 bytes at a time from the
socket with an overall limit of 8_000_000 bytes.
"""
alias Plug.{Conn, Upload}
@behaviour Plug.Parsers
def init(opts) do
{subtypes, opts} = Keyword.pop(opts, :subtypes)
{body_reader, opts} = Keyword.pop(opts, :body_reader, {Conn, :read_body, []})
unless subtypes do
raise ArgumentError, "Image parser expects a :subtypes option"
end
{body_reader, subtypes, opts}
end
def parse(%Conn{method: "PUT"} = conn, "image", subtype, _headers, {body_reader, subtypes, opts}) do
if subtype in subtypes do
conn
|> read(body_reader, opts)
|> save()
else
{:next, conn}
end
end
def parse(conn, _type, _subtype, _headers, _opts) do
{:next, conn}
end
defp read(conn, {mod, fun, args}, opts) do
apply(mod, fun, [conn, opts | args])
end
defp save({:ok, data, conn}) do
path = Upload.random_file!("fileupload")
[ctype | _] = Conn.get_req_header(conn, "content-type")
filename = get_filename(conn)
File.write!(path, data)
uploaded_file = %Plug.Upload{
content_type: ctype,
path: path,
filename: filename,
}
{:ok, %{"file" => uploaded_file}, conn}
end
defp save({:more, _data, conn}) do
{:error, :too_large, conn}
end
defp save({:error, :timeout}) do
raise Plug.TimeoutError
end
defp save({:error, _}) do
raise Plug.BadRequestError
end
defp get_filename(%Plug.Conn{params: params}) do
params
|> Map.get("filename")
|> Path.join()
end
end