# `F3.IO.HTTP`

HTTP byte source.

`open/2` sends one `HEAD` and stores `Content-Length`. `pread/3` sends one
`GET` with `Range: bytes=offset-end` and returns at most the requested
length. Redirects are refused. `lib_urls` in a footer are not requests.

Unsigned options are `headers:` and `timeout_ms:`. `sign:` is a function
`(method, url, headers) -> {:ok, headers} | {:error, F3.Error.t()}` that
the caller uses to attach its own signature. When `endpoint:`, `bucket:`,
`key:`, `region:`, `access_key:`, and `secret_key:` are all set, this
module signs path-style `HEAD` and `GET` with SigV4. The object URL is
`endpoint/bucket/key`. The secret is not stored on the error or printed
by `Inspect`.

# `t`

```elixir
@type t() :: %F3.IO.HTTP{
  closed: boolean(),
  display_url: String.t(),
  headers: [{String.t(), String.t()}],
  signer:
    (atom(), String.t(), [{String.t(), String.t()}] -&gt;
       {:ok, [{String.t(), String.t()}]} | {:error, F3.Error.t()})
    | nil,
  size: non_neg_integer(),
  timeout: pos_integer(),
  url: String.t()
}
```

One HTTP source.

`url` is the request URL. `display_url` is that URL with userinfo removed.
`size` is the `Content-Length` from HEAD. `headers` are the caller headers.
`timeout` is the request timeout in milliseconds. `signer` is the optional
arity-3 function, or `nil`. `closed` drops the signer and headers.

# `close`

Marks the handle closed and drops headers and the signer.

## Examples

    iex> state = %F3.IO.HTTP{
    ...>   url: "http://example.test/a",
    ...>   display_url: "http://example.test/a",
    ...>   size: 4,
    ...>   headers: [{"authorization", "redacted"}],
    ...>   timeout: 10_000,
    ...>   signer: nil
    ...> }
    iex> {:ok, closed} = F3.IO.HTTP.close(state)
    iex> {closed.closed, closed.headers, closed.signer}
    {true, [], nil}

# `open`

```elixir
@spec open(String.t(), keyword()) :: {:ok, t()} | {:error, F3.Error.t()}
```

Opens `url` with one HEAD request.

See the module docs for `opts`. When the six signing keys are set, `url`
is not requested. The path-style object URL is.

A non-http URL, a partial key set, a bad `sign:`, and `timeout_ms` that
is not a positive integer are `:invalid_argument` and do not raise.
Combining `sign:` with the six keys is the same error.

## Examples

    iex> {:error, error} = F3.IO.HTTP.open("ftp://example.test/a")
    iex> error.category
    :invalid_argument

    iex> {:error, error} = F3.IO.HTTP.open("http://example.test/a", timeout_ms: 0)
    iex> error.message
    "timeout_ms must be a positive integer"

    iex> {:error, error} = F3.IO.HTTP.open("http://example.test/a", bucket: "b")
    iex> error.message
    "signed request needs endpoint, bucket, key, region, access_key, and secret_key"

# `pread`

One ranged GET. A closed handle is `:closed`. A negative offset or length
is `:invalid_argument`. A start at or past `size` is `{:ok, ""}` and does
not send a request.

## Examples

    iex> state = %F3.IO.HTTP{
    ...>   url: "http://example.test/a",
    ...>   display_url: "http://example.test/a",
    ...>   size: 4,
    ...>   headers: [],
    ...>   timeout: 10_000,
    ...>   signer: nil,
    ...>   closed: true
    ...> }
    iex> {:error, error} = F3.IO.HTTP.pread(state, 0, 1)
    iex> error.category
    :closed

# `size`

Size from the HEAD response. A closed handle is `:closed`.

## Examples

    iex> state = %F3.IO.HTTP{
    ...>   url: "http://example.test/a",
    ...>   display_url: "http://example.test/a",
    ...>   size: 4,
    ...>   headers: [],
    ...>   timeout: 10_000,
    ...>   signer: nil
    ...> }
    iex> F3.IO.HTTP.size(state)
    {:ok, 4}

---

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