# Proxy

> H3 proxy utilities.

### `fetchWithEvent(event, url, init?)`

Make a fetch request carrying the event's context.

Behavior depends on the target:

An **internal** `url` (starting with `/`) is dispatched via `event.app.fetch()` (sub-request) and never leaves the process. It inherits the incoming request's filtered headers (via `getProxyRequestHeaders`) and runtime metadata (`ip`, `waitUntil`, ...).

An **external** `url` is sent with native `fetch(url, init)` **unchanged** — the event's headers and context are <u>not</u> inherited (forwarding cookies or authorization to arbitrary hosts would be unsafe). A streamed `init.body` is given `duplex: "half"` when unset, which Node's `fetch` requires.

**Security:** Never pass unsanitized user input as the `url`. Callers are responsible for validating and restricting the URL.

### `getProxyRequestHeaders(event)`

Get the request headers object without headers known to cause issues when proxying.

### `proxy(event, target, opts)`

Make a proxy request to a target URL and send the response back to the client.

If the `target` starts with `/`, the request is dispatched internally via `event.app.fetch()` (sub-request) and never leaves the process. This bypasses any external security layer (reverse proxy auth, IP allowlisting, mTLS).

Upstream 3xx responses are passed through to the client by default rather than followed. Set `fetchOptions: { redirect: "follow" }` to follow them instead — but following a redirect with a streamed request body can fail, since the body cannot be replayed once consumed. (Internal sub-requests via `event.app.fetch()` never follow redirects.)

**Limitations** (inherited from `fetch`): upstream response bodies are always decompressed (compression is not preserved end-to-end), the `host` header is rewritten to the target (preserving it via `forwardHeaders: ["host"]` works on Node.js but may be ignored on other runtimes), and unix sockets, TLS options, or connection agents require a runtime-specific escape hatch (e.g. undici's `dispatcher` in `fetchOptions` on Node.js). On browser and service-worker runtimes, `redirect: "manual"` produces an unrelayable opaque-redirect for external targets (a `502` is returned) — set `fetchOptions: { redirect: "follow" }` there.

**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).

### `proxyRequest(event, target, opts)`

Proxy the incoming request to a target URL.

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.

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.

Upstream 3xx responses are passed through to the client by default rather than followed. Set `fetchOptions: { redirect: "follow" }` to follow them instead — but following a redirect with a streamed request body can fail, since the body cannot be replayed once consumed.

**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.

**Example:**

```ts
app.all("/proxy", async (event) => {
  const body = await event.req.clone().json(); // read from the clone
  // ...inspect body...
  return proxyRequest(event, "/target"); // original stream still intact
});
```
