Requests

Query, form, JSON, headers, route parameters

# Requests

`TRequest` is parsed into the arena and **copies nothing**. Every field is a
`TStr` slice into the buffer the bytes arrived in. When the host calls
`Arena.Reset`, both the buffer and the request go in one operation.

## The line

| | |
|---|---|
| `Method` | `hmGet`, `hmPost`, `hmPut`, `hmPatch`, `hmDelete`, `hmHead`, `hmOptions`, `hmUnknown` |
| `MethodStr` | As it arrived |
| `Path` | Percent-decoded. This is what the router matches |
| `RawPath` | As it arrived |
| `QueryString` | Everything after `?` |
| `Target` | Path and query together |
| `VersionMinor` | 1 for HTTP/1.1 |
| `RemoteAddr` | The peer |

```pascal
if Req.Method = hmPost then
  ...
```

> Inside a class, `MethodName` resolves to `TObject.MethodName`. Qualify it:
> `Askr.Http.Types.MethodName(Req.Method)`.

## Query parameters

```pascal
Name := Req.Query('name');              { TStr, percent-decoded, + is space }
Page := Req.Page;                       { ?page=N, clamped to at least 1 }
Limit := Req.IntQuery('limit', 25);
if Req.HasQuery('debug') then
  ...
```

`Page` exists because pagination is the most common place a query parameter
becomes a number.

## Route parameters

Set by the router when a pattern matches.

```pascal
Id := Req.IntParam('id');       { 0 if absent or not a number }
Slug := Req.Param('slug');      { TStr }
if Req.HasParam('id') then
  ...
```

## Form fields

```pascal
Name := Req.Form('name');
if Req.HasForm('subscribe') then
  ...
```

`Form` reads **both** `application/x-www-form-urlencoded` and the ordinary
fields of a `multipart/form-data` body. Without that, a form with a file in
it would make every other field unreachable — and the CSRF token is one of
them.

`HasForm` is not the same as a non-empty `Form`: a checkbox often sends an
empty value, and "present but empty" is different from "absent".

## Files

```pascal
if Req.IsMultipart then
begin
  F := Req.Upload('avatar');
  if not F.IsEmpty then
    Path := F.StoreIn('storage/uploads');
end;
```

See [File uploads](uploads.md).

## Headers

```pascal
Auth := Req.Header('Authorization');    { case-insensitive }
if Req.HasHeader('X-Inertia') then
  ...
```

`HasHeader` is true for a header present with an empty value; `Header` would
return an empty `TStr` for both.

## The body

```pascal nocheck
Req.Body;              { TStr, the raw bytes }
Req.ContentLength;
Req.IsJson;            { application/json, and anything +json }
Req.ContentType;
```

`IsJson` covers `application/ld+json`, `application/problem+json` and the
rest of the `+json` family, not just the exact type.

## JSON into a model

```pascal
uses Askr.Urd.Bind;

C := Arena.New<TCustomer>;
Req.FillInto(C);
if not C.Validate then
  Exit(BackWithErrors(C.Errors));
C.Save;
```

`FillInto` is a class helper in `Askr.Urd.Bind`, not a method on `TRequest`.
Without that split, `Askr.Http` would have to know `Askr.Urd`, and that
would bind the desktop shell and plain JSON services to the data layer.

It reads a JSON body or form fields, matching on **column** names — the
same keys a model goes out with, so `released_on` in and `released_on`
out — and converts to the property's type.

> **The primary key is never filled from a request.** Do not "improve" that.
> Neither are `created_at` and `updated_at` on a model with `S.Timestamps`,
> or `deleted_at` on one with `S.SoftDeletes`: the model sets those
> itself, and a client that added `created_at` to the body used to set it.

### Only the fields the form has

```pascal
Req.FillInto(C, [Customers.Name.Name, Customers.Email.Name]);
```

`FillInto(C)` fills **every** column the model maps that the request
carries. A form with two fields does not stop a client from adding a third
to the body — `is_admin`, a column you hide from JSON — and
the one-argument form sets it. The two-argument form fills the columns it
is given and ignores the rest of the body. Name them with the typed columns
from `askr schema`, as above, so a column that goes away is a compile
error; a name the model does not map raises rather than being skipped,
because a field that silently did not save looks like one that did.

`askr make resource` writes the two-argument form in every controller.

The parsed JSON body is cached per request. The cache cannot be keyed on the
request pointer alone — the arena reuses addresses, so the next request
often lands exactly where the last one was. `Arena.Defer` clears it on
`Reset`.

## The ambient request

```pascal
CurrentRequest;          { the request being served on this thread }
UseRequest(R);           { the host sets it; returns the previous }
```

Set by the host before it calls into your code, so helpers like `Inertia()`
can find it without every controller passing it down.

## Limits and what is refused

| Condition | Status |
|---|---|
| Malformed | 400 |
| URI too long | 414 |
| Headers too large | 431 |
| Body over `MaxBodyBytes` (8 MB default) | 413 |
| HTTP version not 1.x | 505 |
| `Transfer-Encoding: chunked` | 501 |

Chunked request bodies are **refused, not misread**. Obsolete line folding
is refused for the same reason. The body limit is checked against
`Content-Length` before a single byte of body is read.