Building URLs for the server

Copy Markdown View Source

In this guide we'll configure image_pipe_server to serve only signed URLs, then create an Elixir app that builds signed URLs for it with image_pipe_url.

Work through Getting started with the server first. You also need Elixir 1.18 or newer.

Adding a signing key

The server and the app share a secret signing key. In the image-server directory from the server guide, generate one into an environment variable. Keep this terminal open, since the app needs the same key later:

export IMAGE_PIPE_SIGNING_KEY="$(openssl rand -hex 32)"

Start the server as before, now with the key in IPS_URL__KEYS:

docker run -d --name image-server -p 8080:8080 \
  -e IPS_URL__KEYS="$IMAGE_PIPE_SIGNING_KEY" \
  -v "$PWD/config.toml:/etc/image_pipe/config.toml:ro" \
  -v "$PWD/images:/data/images:ro" \
  ghcr.io/hlindset/image_pipe_server:0.1

With a signing key set, the server refuses URLs without a valid signature. http://localhost:8080/w=400/src/photo.jpg, which worked before, now answers 403 with the body invalid signature.

Creating the app

In the same terminal, create an app next to image-server:

cd ..
mix new storefront
cd storefront

Add the URL builder to the dependencies in mix.exs:

# mix.exs
defp deps do
  [
    {:image_pipe_url, "~> 0.1.0"}
  ]
end

Fetch it:

mix deps.get

image_pipe_url only builds URLs. It processes no images, so the app needs no image libraries.

Building a signed URL

Start iex from the same terminal, so it can read IMAGE_PIPE_SIGNING_KEY:

iex -S mix

The URL configuration holds the server's address and the signing key:

iex> url_config =
...>   ImagePipe.URL.config(
...>     base_url: "http://localhost:8080",
...>     keys: [System.fetch_env!("IMAGE_PIPE_SIGNING_KEY")]
...>   )

Let's build a signed URL for a 400×300 crop of the photo:

iex> thumbnail =
...>   ImagePipe.URL.new(url_config)
...>   |> ImagePipe.URL.group(resize: [width: 400, height: 300, fit: :cover])
iex> ImagePipe.URL.url!(thumbnail, "photo.jpg")
"http://localhost:8080/sig=4awb0-qKf4y4nPx6pMtsC7tzfn2MAY3gYzpUJ1ZeUGQ/w=400/h=300/fit=cover/src/photo.jpg"

The sig= value is computed from your key, so yours is different. Open your URL in a browser:

GET /sig=.../w=400/h=300/fit=cover/src/photo.jpg (in Chrome)
200 OK, content-type: image/avif, 400×300

Notice that the sig= segment signs everything after it, and the server checks it with the same key before serving the image.

Changing the options

Edit the URL in the browser and change w=400 to w=800. The server answers 403 with the body invalid signature, because the signature no longer matches the options. Every change needs a URL built with the key:

iex> wide = ImagePipe.URL.new(url_config) |> ImagePipe.URL.group(resize: [width: 800])
iex> ImagePipe.URL.url!(wide, "photo.jpg")
"http://localhost:8080/sig=2J7_YYeGtqkBDb7-9SBMWXHF3613bvJTgA91QG81oL4/w=800/src/photo.jpg"

Open it:

GET /sig=.../w=800/src/photo.jpg
200 OK, content-type: image/avif, 800×533

When you're done, stop and remove the container:

docker rm -f image-server

Next steps

We ran a server that accepts only signed URLs and built signed URLs for it from an Elixir app. From here:

The server reads originals only from its own sources. Files the app stores need to be somewhere the server can read them, such as S3 or an HTTP origin. Deploying image_pipe_server covers running the server in production.