multipart parsing, request body access, and chunked input stream support
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
# Request Body
|
||||
|
||||
`req.body()` returns a `RequestBody` — a lazy accessor that exposes the HTTP request body in
|
||||
two mutually exclusive modes. Choose one per handler based on the payload size and how you
|
||||
intend to consume it.
|
||||
|
||||
---
|
||||
|
||||
## Two modes
|
||||
|
||||
### `bytes()` — materialise
|
||||
|
||||
```java
|
||||
byte[] body = req.body().bytes();
|
||||
```
|
||||
|
||||
Reads the full body into a `byte[]` and caches the result. Safe to call multiple times — the
|
||||
second call returns the same cached array, no I/O is performed again.
|
||||
|
||||
Use for:
|
||||
- JSON parsing (`new String(body, UTF_8)` then parse)
|
||||
- Small form data
|
||||
- Any payload that must be inspected in full before responding
|
||||
|
||||
**Limit:** throws `IllegalStateException` if `Content-Length` exceeds `Integer.MAX_VALUE`
|
||||
(~2 GB). For larger bodies use `stream()`.
|
||||
|
||||
**Chunked bodies:** reads until `ChunkedInputStream` signals EOF, then caches.
|
||||
|
||||
---
|
||||
|
||||
### `stream()` — zero-copy
|
||||
|
||||
```java
|
||||
InputStream in = req.body().stream();
|
||||
```
|
||||
|
||||
Returns a bounded `InputStream` without allocating the full body upfront.
|
||||
|
||||
Use for:
|
||||
- Large file uploads
|
||||
- Bodies passed directly to disk, a database, or another stream
|
||||
- Multipart parsing (via `Multipart.of(req)` which calls this internally)
|
||||
|
||||
**Fixed-length bodies:** a `SequenceInputStream` of any pre-buffered header bytes (from the
|
||||
`RequestParser` read buffer, already in memory) followed by a bounded view of the socket
|
||||
stream. The small pre-buffered slice costs nothing extra.
|
||||
|
||||
**Chunked bodies:** returns the raw `ChunkedInputStream` directly. It de-chunks on the fly —
|
||||
reads the hex chunk-size line, the data, and the trailing CRLF — and signals EOF at the end
|
||||
of the last chunk, leaving the socket positioned correctly for the next keep-alive request.
|
||||
|
||||
**After `bytes()`:** if the body was already materialised, `stream()` returns a fresh
|
||||
`ByteArrayInputStream` over the cached array. No I/O is performed.
|
||||
|
||||
---
|
||||
|
||||
## Mutual exclusivity
|
||||
|
||||
```java
|
||||
// SAFE — pick one mode
|
||||
byte[] b = req.body().bytes();
|
||||
InputStream s = req.body().stream();
|
||||
|
||||
// UNSAFE — bytes() then stream() is safe (stream() wraps the cache)
|
||||
// UNSAFE — stream() partially read, then bytes() → undefined results
|
||||
```
|
||||
|
||||
The rule: if you call `stream()` first and partially read it, calling `bytes()` afterwards
|
||||
will either throw (if `contentLength` > 2 GB) or read only the remaining socket bytes into
|
||||
the array, missing the already-consumed portion.
|
||||
|
||||
---
|
||||
|
||||
## Checking the size
|
||||
|
||||
```java
|
||||
long size = req.body().contentLength();
|
||||
```
|
||||
|
||||
- **> 0** — fixed-length body; exact byte count
|
||||
- **== 0** — empty body (`Content-Length: 0` or no body present)
|
||||
- **== -1** — `Transfer-Encoding: chunked`; size is unknown upfront
|
||||
|
||||
```java
|
||||
boolean empty = req.body().isEmpty(); // true when contentLength == 0
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Decision guide
|
||||
|
||||
| Payload size | Encoding | Use |
|
||||
|--------------------|-----------|----------------|
|
||||
| < 2 GB, fully needed | any | `bytes()` |
|
||||
| Any size, pass-through | any | `stream()` |
|
||||
| Multipart form | any | `Multipart.of(req)` (uses `stream()` internally) |
|
||||
| Size unknown | chunked | `stream()` |
|
||||
Reference in New Issue
Block a user