HTTP client
Calling other services, with certificate verification
# HTTP client
```pascal
uses Askr.Http.Client;
K := THttpClient.Create;
try
K.WithBearer(CfgOrFail('api.token'));
R := K.Get('https://api.example.com/v1/things');
if R.Ok then
Handle(R.Body);
finally
K.Free;
end;
```
## Requests
```pascal
K.Get(Url);
K.Delete(Url);
K.Post(Url, Body); { application/json by default }
K.Put(Url, Body, 'text/plain');
K.Patch(Url, Body);
K.Request(Method, Url, Body, ContentType);
```
```pascal
K.WithHeader('X-Request-Id', Id);
K.WithBearer(Token); { Authorization: Bearer ... }
K.ClearHeaders;
```
Headers set on the client go with every request it makes.
## Responses
```pascal nocheck
R.Status; R.Reason; R.Ok; { 200-299 }
R.Body;
R.Header('Content-Type'); { case-insensitive }
R.HasHeader('Location');
R.IsJson;
R.FinalUrl; R.Redirects; R.ElapsedMs;
```
A 404 is **a response, not an exception**. Exceptions are for the request
never completing: DNS, connection, TLS, a malformed reply, a body over the
cap.
## TLS
**The certificate is verified — both the chain and the host name.**
The second is not a detail. `SSL_VERIFY_PEER` on its own accepts a genuine,
valid certificate for *any* domain, and then there is nothing left of the
protection. Both are checked, and that is proven against the real internet:
`wrong.host.badssl.com` and `expired.badssl.com` are both rejected.
```pascal
K.Insecure := True; { a self-signed certificate in development }
```
It logs a warning on **every** request that uses it, on purpose. A setup
that silently stopped verifying is exactly the mistake nobody notices.
HTTPS needs OpenSSL. See [TLS](tls.md).
## Streaming
For Server-Sent Events, and for long responses that should not sit in memory
before the caller sees any of them:
```pascal
function TListener.Chunk(const S: string): Boolean;
begin
Write(S);
Result := not Cancelled; { False stops the read }
end;
R := K.Stream('POST', Url, Body, 'application/json', Listener.Chunk);
```
`R.Body` is empty when streaming — the callback got it.
> Returning `False` stops the read everywhere, including from the very first
> chunk. The first version ignored the answer from the chunk that was
> already in the buffer after the head and kept reading; for an SSE stream
> that means the listener never stops listening.
## Redirects
Followed, up to `MaxRedirects` (5). **307 and 308 keep the method and the
body; 301, 302 and 303 become GET** — that is the whole reason 307 and 308
exist.
```pascal
K.MaxRedirects := 0; { return the 302 itself }
```
A chain that does not end raises rather than hanging.
## Limits
```pascal
K.ConnectTimeoutMs := 10000;
K.ReadTimeoutMs := 30000;
K.MaxResponseBytes := 32 * 1024 * 1024;
K.UserAgent := 'shop/1.0';
```
`MaxResponseBytes` is a **stop, not an optimisation**: a response with
neither `Content-Length` nor chunked framing can in principle go on forever.
## Chunked
Decoded transparently. Which matters, because that is what every server uses
when it does not know the length in advance — nearly always for a streamed
API.
## Name resolution
`getaddrinfo` from libc, not FPC's `netdb`.
> netdb has its own DNS implementation that reads `/etc/resolv.conf` and
> speaks UDP itself, and it fails where the system manages: in a container
> with Docker Desktop's resolver it gave up entirely while `getent hosts`
> answered. `getaddrinfo` goes the way the system goes — nsswitch,
> `/etc/hosts`, DNS, mDNS. netdb remains as a fallback.
IPv4 only. The rest of Askr uses `TInetSockAddr`, and IPv6 needs a different
address family the whole way — a documented limit in the server since step 1,
which the client inherits.
## Compression
The client asks for `identity`, so it never receives gzip. Without zlib it
could not decompress, and a client that asks for something it cannot read is
a bug waiting. Binding zlib to save bandwidth on an API call is the wrong
trade when the binary is supposed to start on a machine without it.
## What is not here
HTTP/2, proxy support, a cookie jar, automatic retry. All four are real
needs for somebody; none of them was for what was queued behind this file.