# `Tspex.HTTP`
[🔗](https://github.com/openspb/tspex/blob/v0.1.0/lib/tspex/http.ex#L1)

The HTTP transport of `Tspex.stamp/2`: one `POST` of a time-stamp request
to a TSA (RFC 3161, section 3.4), written on `:gen_tcp` and `:ssl`.

It is small on purpose, because it only has to do one thing and put
limits on it that `:httpc` does not offer. For anything else (a proxy, a
client certificate with an agent, HTTP/2, your own retries) pass your own
`:transport` function to `Tspex.stamp/2`, a function of the URL, the
request in DER and the options that returns `{:ok, response_der}` or
`{:error, reason}`, and use Req, Finch or Mint inside it.

What it does:

  * `POST` with `Content-Type: application/timestamp-query`, `Accept:
    application/timestamp-reply`, `User-Agent: tspex/<version>` and
    `Connection: close`; one connection for each request.
  * **`https` verifies the server**: `verify_peer`, the system CA
    certificates (`:public_key.cacerts_get/0`) and the name of the URL
    against the certificate. `http` is accepted, because the token
    authenticates itself and many TSAs speak only `http`; the cost is
    that the hash of your data travels in the clear (a matter of privacy,
    not of integrity).
  * **Only the status 200 is a success.** Any other status ends the
    request at once, without reading its body, and is `{:http_status,
    n}`.
  * **The body is limited** by `:max_response_bytes`. A `Content-Length`
    above it is refused before a byte of the body is read, and a chunked
    or close-delimited body is cut at the limit, so a server cannot make
    you allocate what it wants.
  * **No redirect** unless you raise `:max_redirects`, and then only 307
    and 308 (which keep the method and the body), never from `https` to
    `http`, and the `:headers` you gave are not sent to another host.
  * **The type of the body is not required**: some TSAs answer with
    `text/plain`. What decides is that the bytes are a valid DER
    response, which `Tspex.verify/3` checks.
  * The time of `:timeout` is for the whole exchange.

Options:

  * `:headers` - extra headers as `{name, value}` strings, for
    authentication. They replace `Content-Type`, `Accept` and
    `User-Agent` of the same name; `Host`, `Content-Length`, `Connection`
    and `Transfer-Encoding` are refused. A name with anything that is not
    a token, or a value with a line break, raises `ArgumentError`.
  * `:timeout` - milliseconds for the whole exchange, 30 000.
  * `:max_redirects` - 0.
  * `:max_response_bytes` - 1 MiB.
  * `:ssl` - options for `:ssl.connect/4` that replace the defaults: a
    `cacerts:` or `cacertfile:` for a private CA, or `certfile:` and
    `keyfile:` for a client certificate. `verify: :verify_none` is yours
    to choose, and the token is still verified, but then anyone on the
    path can read your request.

The reasons of `{:error, reason}`, which `Tspex.stamp/2` turns into the
`detail` of a `:transport` error: `{:http_status, n}`, `:timeout`,
`:too_large`, `{:connect, reason}` (no address, connection refused),
`{:tls, reason}` (the handshake, the certificate or the name) and
`{:other, reason}` (a response that is not HTTP, a connection closed
before the end of the body).

Needs the `:ssl` application only for `https` URLs.

# `reason`

```elixir
@type reason() ::
  {:http_status, pos_integer()}
  | :timeout
  | :too_large
  | {:connect, term()}
  | {:tls, term()}
  | {:other, term()}
```

Why the request failed.

# `post`

```elixir
@spec post(String.t(), binary(), keyword()) :: {:ok, binary()} | {:error, reason()}
```

Sends `body` to `url` and returns the body of the answer.

    {:ok, response_der} = Tspex.HTTP.post("http://tsa.example.com/tsp", request.der)

See the module documentation for the options. A `url` that is not `http`
or `https` with a host, or a bad option, raises `ArgumentError`.

---

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