# `Fil.Plug`
[🔗](https://github.com/pehbehbeh/fil/blob/v0.1.0/lib/fil/plug.ex#L2)

Serves the files of a disk over HTTP: the URLs `Fil.Plugin.URL` signs, or with `public: true`, every file.

Signed URLs make direct downloads and uploads work on every disk: local and memory disks, which can't sign URLs
themselves, and S3 disks whose files should go through your application.

In a Phoenix app, put it in the endpoint before `Plug.Parsers`, at the path the plugin's `:base_url` points to:

    plug Fil.Plug, at: "/storage/uploads", disk: &MyApp.Storage.uploads/0

The `:base_url` of the disk's `Fil.Plugin.URL` is then `"http://localhost:4000/storage/uploads"`, and every
URL `Fil.signed_url/3` builds for it is served here. To run plugs of your own first, such as authentication, use
a router instead (see [In a router](#module-in-a-router)).

What it answers:

  * `GET` and `HEAD` on a URL signed for `:get` return the file, with its stored content type or one guessed from
    the extension
  * `PUT` on a URL signed for `:put` writes the request body, with the request's `content-type`, the same as a
    presigned PUT on S3. Plugins attached to the disk run as for any other write
  * a request that doesn't match its signature, or comes after the URL expired, gets a `403`
  * an upload larger than `:max_body_size` gets a `413`, and one whose body something else already read (see
    [In a router](#module-in-a-router)) a `400`. Neither writes anything
  * a missing file gets a `404`, and so does a file the storage denies access to, so a client can't tell which
    files exist
  * a failed write gets a `409` if the file already exists, `507` if the storage is full and `503` if it's
    unavailable. Any other error is a `500` with a generic body, and its message goes to the `Logger`

Requests pass through untouched when the disk doesn't sign URLs with `Fil.Plugin.URL` (and the plug isn't public).
An S3 disk without it signs URLs that go to S3 directly, so the plug can stay in the endpoint when production uses
S3.

## Public disks

With `public: true`, the plug serves downloads without a signature, like `Plug.Static` for a disk:

    plug Fil.Plug, at: "/avatars", disk: &MyApp.Storage.avatars/0, public: true

`GET /avatars/1.png` then returns `1.png` from the disk, on every adapter. There are no directory listings, and a
path can't leave the disk root. With `Fil.Plugin.URL` and `base_url: "http://localhost:4000/avatars"` on the disk,
`Fil.url/2` builds these URLs. Uploads still need a signed URL, and without a `:secret` for `Fil.Plugin.URL` on the
disk, a `PUT` gets a `403`.

## In a router

`forward` runs the plug behind a router pipeline, for example to let only signed-in users download from a public
disk:

    pipeline :storage do
      plug :require_authenticated_user
    end

    scope "/storage" do
      pipe_through :storage
      forward "/avatars", Fil.Plug, disk: &MyApp.Storage.avatars/0, public: true
    end

`forward` removes `/storage/avatars` from the path, so the plug needs no `:at`. The `:base_url` of
`Fil.Plugin.URL` is still the full URL, `"http://localhost:4000/storage/avatars"`, because signatures cover the
whole request path.

Two things in a Phoenix app get in the way of uploads there:

  * `Plug.Parsers` in the endpoint reads the bodies it has a parser for before the router runs (in a new Phoenix
    app JSON, form and multipart bodies). A `PUT` with one of those content types reaches the plug without its
    body and gets a `400` (the plug compares what it can read with the `content-length`). Other content types,
    such as `image/png` or `application/pdf`, pass through unread. Use the endpoint for uploads in any format.
  * the `:browser` pipeline's `protect_from_forgery` rejects a `PUT` without a CSRF token. Use a pipeline of your
    own, as above.

Needs [Plug](https://plug.hexdocs.pm), an optional dependency of `Fil`.

## Options

* `:disk` - Required. The disk to serve: a `Fil.Disk`, or a function or `{module, function, args}` that returns one. Use a
  function when the disk comes from runtime config, because plug options are compiled.

* `:at` (`t:String.t/0`) - The path to serve under, e.g. `"/storage/uploads"`. Requests outside it pass through untouched, which is
  how the plug sits in an endpoint. Without it, the plug serves every request it gets, as with `forward`.

* `:public` (`t:boolean/0`) - Serves `GET` and `HEAD` without a signature, so every file on the disk can be downloaded by anyone who
  knows its path. That's where the URLs of `Fil.url/2` point. Uploads still need a URL signed by
  `Fil.Plugin.URL`. The default value is `false`.

* `:max_body_size` (`t:pos_integer/0`) - The largest upload in bytes, 100 MiB by default. A larger `PUT` gets a `413`, and nothing is written.
  Uploads are read into memory whole before they're written, so keep it at what your server can hold. The default value is `104857600`.

