Skip to content

Commit 7eb018e

Browse files
committed
docs(proxy): add note about reading body
1 parent 84244b4 commit 7eb018e

2 files changed

Lines changed: 25 additions & 0 deletions

File tree

docs/2.utils/5.proxy.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,18 @@ Proxy the incoming request to a target URL.
3434

3535
If the `target` starts with `/`, the request is handled internally by the app router via `event.app.fetch()` instead of making an external HTTP request.
3636

37+
The request body is streamed to the target without buffering. Per the Fetch standard, a request body can only be consumed once, so reading it beforehand (e.g. via `readBody()`, `readFormData()`, or body-reading middleware) locks the stream and proxying fails. If you need to inspect the body and still proxy it, read from a clone and leave the original event untouched.
38+
3739
**Security:** Never pass unsanitized user input as the `target`. Callers are responsible for validating and restricting the target URL (e.g. allowlisting hosts, blocking internal paths, enforcing protocol). Consider using `bodyLimit()` middleware to prevent large request bodies from consuming excessive resources when proxying untrusted input.
3840

41+
**Example:**
42+
43+
```ts
44+
app.all("/proxy", async (event) => {
45+
const body = await event.req.clone().json(); // read from the clone
46+
// ...inspect body...
47+
return proxyRequest(event, "/target"); // original stream still intact
48+
});
49+
```
50+
3951
<!-- /automd -->

src/utils/proxy.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,24 @@ export interface ProxyOptions {
2727
* If the `target` starts with `/`, the request is handled internally by the app router
2828
* via `event.app.fetch()` instead of making an external HTTP request.
2929
*
30+
* The request body is streamed to the target without buffering. Per the Fetch
31+
* standard, a request body can only be consumed once, so reading it beforehand
32+
* (e.g. via `readBody()`, `readFormData()`, or body-reading middleware) locks
33+
* the stream and proxying fails. If you need to inspect the body and still
34+
* proxy it, read from a clone and leave the original event untouched.
35+
*
3036
* **Security:** Never pass unsanitized user input as the `target`. Callers are
3137
* responsible for validating and restricting the target URL (e.g. allowlisting
3238
* hosts, blocking internal paths, enforcing protocol). Consider using
3339
* `bodyLimit()` middleware to prevent large request bodies from consuming
3440
* excessive resources when proxying untrusted input.
41+
*
42+
* @example
43+
* app.all("/proxy", async (event) => {
44+
* const body = await event.req.clone().json(); // read from the clone
45+
* // ...inspect body...
46+
* return proxyRequest(event, "/target"); // original stream still intact
47+
* });
3548
*/
3649
export async function proxyRequest(
3750
event: H3Event,

0 commit comments

Comments
 (0)