# `StaticMaps.Source.HTTP`
[🔗](https://gitlab.com/tristanperalta/staticmaps/blob/v0.1.0/lib/static_maps/source/http.ex#L1)

A source that fetches each tile from a URL.

`use StaticMaps.Source.HTTP` and implement `c:url/2` and `c:StaticMaps.Source.info/1`;
this module supplies `fetch/2` and `fetch_cacheable/3`:

    defmodule MyApp.Tiles do
      use StaticMaps.Source.HTTP

      @impl true
      def url(%{z: z, x: x, y: y}, _opts), do: "https://tiles.example.com/#{z}/#{x}/#{y}.png"

      @impl true
      def info(_opts), do: %{attribution: "© Example", tile_size: 256, zoom: 0..19, scales: [1]}
    end

To add headers a server needs, such as an API key, implement `c:headers/2`. To replace
how tiles are fetched entirely, override `fetch_cacheable/3`: it is the one function
every fetch goes through, cached or not. `fetch/2` cannot be overridden, because the
cache never calls it, so an override there would be silently skipped.

Every request:

  * sends `User-Agent: StaticMaps/<version> (+<contact>)`, with the contact from
    `config :staticmaps, contact: "…"`, which no source or option can replace;
  * has a connect timeout and a receive timeout;
  * is retried with backoff on connection errors and on `408`, `429`, `500`, `502`,
    `503` and `504`, honouring `Retry-After` up to a limit and giving up rather than
    waiting longer;
  * counts as a tile only when the response is `200` with an `image/*` content type.

## Configuration

    config :staticmaps, :http,
      connect_timeout: 5_000,
      receive_timeout: 10_000,
      max_retries: 2,
      retry_base: 500,
      max_retry_after: 10_000

`retry_base` is the first backoff in milliseconds, doubling on each retry.
`max_retry_after` is the longest `Retry-After` the fetcher will wait, in milliseconds.

# `headers`

```elixir
@callback headers(StaticMaps.Source.tile(), opts :: keyword()) :: [
  {String.t(), String.t()}
]
```

Extra request headers for `tile`; none by default. A `user-agent` here is ignored.

# `url`

```elixir
@callback url(StaticMaps.Source.tile(), opts :: keyword()) :: String.t()
```

The URL of `tile`.

# `get`

```elixir
@spec get(String.t()) :: {:ok, binary()} | {:error, term()}
```

Fetches `url` and returns the body of a `200` image response.

Errors are `{:http_status, status}`, `{:not_an_image, content_type}`, or
`{:transport, reason}` for a connection that failed or timed out.

# `get_cacheable`

```elixir
@spec get_cacheable(String.t(), StaticMaps.Source.validators(), [
  {String.t(), String.t()}
]) ::
  {:ok, binary(), StaticMaps.Source.cache_meta()}
  | {:not_modified, StaticMaps.Source.cache_meta()}
  | {:error, term()}
```

Like `get/1`, but conditional on `validators`, and returns how long the response may be
cached.

With an `:etag` or `:last_modified` from an earlier response, the request carries
`If-None-Match` or `If-Modified-Since`, and a `304` returns `{:not_modified, meta}`.
See `t:StaticMaps.Source.cache_meta/0` for `meta`. `headers` are sent too, except any
`user-agent`.

# `user_agent`

```elixir
@spec user_agent() :: String.t()
```

The `User-Agent` sent with every tile request.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
