# HTTP Client CLI — Response

The `Response` object holds the data received from an HTTP server after a request is completed. Each `Request` has a paired `Response` that is automatically populated during response decoding.

```php
use Bootgly\WPI\Nodes\HTTP_Client_CLI\Request\Response;
```

## HTTP

`protocol`: The HTTP protocol version of the response.

```php
$Response->protocol; // 'HTTP/1.1'
```

`code`: The HTTP status code.

```php
$Response->code; // 200
```

`status`: The HTTP status reason phrase.

```php
$Response->status; // 'OK'
```

### Special status values

`code === 0` means no HTTP response was produced; `status` names why:

| `code` | `status` | Meaning |
|---|---|---|
| `0` | `'Timeout'` | The request timed out before receiving a response. |
| `0` | `'Connection Failed'` / `'Connection Lost'` / `'Connection Closed'` | The connection failed, was lost, or was closed before a response head arrived. |
| `0` | `'Truncated Response'` | The connection closed before the declared body ended. |
| `0` | `'Response Too Large'` | The response passed `maxResponseBytes` (16 MiB by default). |
| `0` | `'Response Header Fields Too Large'` | The response head passed its 64 KiB cap. |
| `0` | `'Invalid Response'` | The status line, a header field, `Content-Length` or `Transfer-Encoding` is not valid HTTP/1.x framing, or more than 64 interim (1xx) responses came before the final one (HTTP/1.1). |
| `0` | `'Invalid Chunked Encoding'` | The chunked framing is not valid. |

A status line without a reason phrase (`HTTP/1.1 200`) is valid: `code` is `200` and `status` is `''`. A code from 600 to 999 is handled like a server error: the response is framed normally and `code` keeps the value the upstream sent.

## Headers

Access response headers through the `Header` sub-object or via the `headers` property hook:

```php
// @ Get all headers as array (keys are lowercase)
$Response->headers;
// ['content-type' => 'application/json', 'x-request-id' => 'abc123', ...]

// @ Get a single header value
$Response->Header->get('Content-Type'); // 'application/json'

// @ Get a header (case-insensitive)
$Response->Header->get('content-type'); // 'application/json'

// @ Get all values for a multi-value header (e.g. Set-Cookie)
$Response->Header->collect('Set-Cookie');
// ['session=abc; Path=/', 'theme=dark; Path=/']
```

### Header API

| Method | Signature | Description |
|---|---|---|
| `get` | `get (string $name): null\|string` | Get a header value. Returns comma-joined string for multi-value headers. Case-insensitive. |
| `collect` | `collect (string $name): array` | Get all values for a header as an array. Use for headers like `Set-Cookie` that should not be combined. |

### Multi-value headers

Headers that appear multiple times in the response (e.g. `Set-Cookie`) are stored as arrays:

```php
// @ If the response contains:
// Set-Cookie: session=abc; Path=/
// Set-Cookie: theme=dark; Path=/

$Response->Header->get('Set-Cookie');
// 'session=abc; Path=/, theme=dark; Path=/'

$Response->Header->collect('Set-Cookie');
// ['session=abc; Path=/', 'theme=dark; Path=/']
```

### OWS Trimming

Response header values are trimmed of optional whitespace (SP/HTAB) per RFC 9110:

```php
// @ Raw header: "Content-Type:   application/json  "
$Response->Header->get('Content-Type'); // 'application/json'
```

## Body

Access the response body through the `Body` sub-object or via the `body` property hook:

```php
// @ Get body as raw string
$Response->body; // '{"id":1,"name":"Bootgly"}'

// @ Body metadata
$Response->Body->length;      // 25 (content length in bytes)
$Response->Body->downloaded;  // 25 (bytes downloaded so far)
$Response->Body->waiting;     // false (true while body is incomplete)
```

### Decoding

Decode the response body into structured data:

```php
// @ Decode JSON body
$data = $Response->Body->decode('json');
// ['id' => 1, 'name' => 'Bootgly']

// @ Decode JSON as object
$data = $Response->Body->decode('json', associative: false);
// stdClass { id: 1, name: 'Bootgly' }

// @ Default (returns raw string)
$raw = $Response->Body->decode('raw');
// '{"id":1,"name":"Bootgly"}'
```

| Type | Returns | Description |
|---|---|---|
| `'json'` | `mixed` | JSON-decoded value. Returns `null` if body is empty or invalid JSON. |
| `default` | `string` | Raw body string. |

## Connection State

`closeConnection`: Whether the server indicated the connection should be closed.

```php
$Response->closeConnection; // false (keep-alive) or true (Connection: close)
```

## Transfer Encoding

The client automatically handles different transfer encodings:

| Encoding | Detection | Behavior |
|---|---|---|
| **Chunked** | `Transfer-Encoding: chunked` | Decodes chunks, assembles final body, handles trailers. |
| **Content-Length** | `Content-Length: N` | Reads exactly N bytes for the body. |
| **Close-delimited** | No Content-Length, no chunked | Reads until connection closes. |

Framing is strict: `Content-Length` must be one exact number (identical repeats are accepted; a
sign, a suffix, an overflow or disagreeing repeats fail with `'Invalid Response'`), and a response
carrying both `Transfer-Encoding` and `Content-Length` is read by `Transfer-Encoding` with its
connection closed afterwards (`closeConnection` is `true`).

All decoding is transparent — `$Response->body` always contains the final, decoded body regardless of transfer encoding.

Every body is collected read by read and its head is parsed once, so a large `Content-Length` or close-delimited body costs about what a chunked one does. A `Content-Length` or close-delimited body that never completes — truncated, timed out — still reports the bytes that arrived in `Body->raw` and `Body->downloaded`, with `Body->waiting` left `true`.

## Reset

Reset the response to its default state for reuse:

```php
$Response->reset();
// protocol = 'HTTP/1.1'
// code = 0
// status = ''
// closeConnection = false
// Headers and Body are also reset.
```

## Complete Example

```php
use Bootgly\WPI\Nodes\HTTP_Client_CLI;
use Bootgly\WPI\Nodes\HTTP_Client_CLI\Configs;


$Client = new HTTP_Client_CLI;
$Client->configure(
   new Configs(host: 'api.example.com', port: 443, secure: [])
);

$Response = $Client->request(
   method: 'GET',
   URI: '/users/1',
   headers: ['Accept' => 'application/json']
);

// ? Check status
if ($Response->code === 200) {
   // ? Read headers
   $contentType = $Response->Header->get('Content-Type');
   $requestId = $Response->Header->get('X-Request-Id');

   // ? Decode body
   $user = $Response->Body->decode('json');
   echo $user['name']; // 'Bootgly'
}
else if ($Response->code === 0) {
   echo "Request failed: {$Response->status}"; // 'Timeout' or ''
}
else {
   echo "Error: {$Response->code} {$Response->status}";
}
```
