Skip to content

Commit

Permalink
types: add agent and dispatcher options (node-specific) (#308)
Browse files Browse the repository at this point in the history
  • Loading branch information
pi0 committed Aug 28, 2024
1 parent 3baf1cc commit 6aff08e
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 34 deletions.
104 changes: 71 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,6 @@ const { ofetch } = require("ofetch");
We use [conditional exports](https://nodejs.org/api/packages.html#packages_conditional_exports) to detect Node.js
and automatically use [unjs/node-fetch-native](https://github.com/unjs/node-fetch-native). If `globalThis.fetch` is available, will be used instead. To leverage Node.js 17.5.0 experimental native fetch API use [`--experimental-fetch` flag](https://nodejs.org/dist/latest-v17.x/docs/api/cli.html#--experimental-fetch).

### `keepAlive` support

By setting the `FETCH_KEEP_ALIVE` environment variable to `true`, an HTTP/HTTPS agent will be registered that keeps sockets around even when there are no outstanding requests, so they can be used for future requests without having to re-establish a TCP connection.

**Note:** This option can potentially introduce memory leaks. Please check [node-fetch/node-fetch#1325](https://github.com/node-fetch/node-fetch/pull/1325).

## ✔️ Parsing Response

`ofetch` will smartly parse JSON and native values using [destr](https://github.com/unjs/destr), falling back to the text if it fails to parse.
Expand Down Expand Up @@ -285,55 +279,99 @@ await ofetch("/movies", {
});
```

## 💡 Adding a HTTP(S) / Proxy Agent
## 🍣 Access to Raw Response

If you need to access raw response (for headers, etc), can use `ofetch.raw`:

```js
const response = await ofetch.raw("/sushi");

If you need use a HTTP(S) / Proxy Agent, you can (for Node.js only):
// response._data
// response.headers
// ...
```

### Node >= v18
## 🌿 Using Native Fetch

Add `ProxyAgent` to `dispatcher` option with `undici`
As a shortcut, you can use `ofetch.native` that provides native `fetch` API

```js
import { ofetch } from 'ofetch'
import { ProxyAgent } from 'undici'
const json = await ofetch.native("/sushi").then((r) => r.json());
```

await ofetch("/api", {
dispatcher: new ProxyAgent("http://example.com"),
});
## 🕵️ Adding HTTP(S) Agent

In Node.js (>= 18) environments, you can provide a custom dispatcher to intercept requests and support features such as Proxy and self-signed certificates. This feature is enabled by [undici](https://undici.nodejs.org/) built-in Node.js. [read more](https://undici.nodejs.org/#/docs/api/Dispatcher) about the Dispatcher API.

Some available agents:

- `ProxyAgent`: A Proxy Agent class that implements the Agent API. It allows the connection through a proxy in a simple way. ([docs](https://undici.nodejs.org/#/docs/api/ProxyAgent))
- `MockAgent`: A mocked Agent class that implements the Agent API. It allows one to intercept HTTP requests made through undici and return mocked responses instead. ([docs](https://undici.nodejs.org/#/docs/api/MockAgent))
- `Agent`: Agent allows dispatching requests against multiple different origins. ([docs](https://undici.nodejs.org/#/docs/api/Agent))

**Example:** Set a proxy agent for one request:

```ts
import { ProxyAgent } from "undici";
import { ofetch } from "ofetch";

const proxyAgent = new ProxyAgent("http://localhost:3128");
const data = await ofetch("https://icanhazip.com", { dispatcher: proxyAgent });
```

### Node < v18
**Example:** Create a custom fetch instance that has proxy enabled:

Add `HttpsProxyAgent` to `agent` option with `https-proxy-agent`
```ts
import { ProxyAgent, setGlobalDispatcher } from "undici";
import { ofetch } from "ofetch";

```js
import { HttpsProxyAgent } from "https-proxy-agent";
const proxyAgent = new ProxyAgent("http://localhost:3128");
const fetchWithProxy = ofetch.create({ dispatcher: proxyAgent });

await ofetch("/api", {
agent: new HttpsProxyAgent("http://example.com"),
});
const data = await fetchWithProxy("https://icanhazip.com");
```

## 🍣 Access to Raw Response
**Example:** Set a proxy agent for all requests:

If you need to access raw response (for headers, etc), can use `ofetch.raw`:
```ts
import { ProxyAgent, setGlobalDispatcher } from "undici";
import { ofetch } from "ofetch";

```js
const response = await ofetch.raw("/sushi");
const proxyAgent = new ProxyAgent("http://localhost:3128");
setGlobalDispatcher(proxyAgent);

// response._data
// response.headers
// ...
const data = await ofetch("https://icanhazip.com");
```

## Native fetch
**Example:** Allow self-signed certificates (USE AT YOUR OWN RISK!)

As a shortcut, you can use `ofetch.native` that provides native `fetch` API
```ts
import { Agent } from "undici";
import { ofetch } from "ofetch";

```js
const json = await ofetch.native("/sushi").then((r) => r.json());
// Note: This makes fetch unsecure against MITM attacks. USE AT YOUW OWN RISK!
const unsecureAgent = new Agent({ connect: { rejectUnauthorized: false } });
const unsecureFetch = ofetch.create({ dispatcher: unsecureAgent });

const data = await unsecureFetch("https://www.squid-cache.org/");
```

On older Node.js version (<18), you might also use use `agent`:

```ts
import { HttpsProxyAgent } from "https-proxy-agent";

await ofetch("/api", {
agent: new HttpsProxyAgent("http://example.com"),
});
```

### `keepAlive` support (only works for Node < 18)

By setting the `FETCH_KEEP_ALIVE` environment variable to `true`, an HTTP/HTTPS agent will be registered that keeps sockets around even when there are no outstanding requests, so they can be used for future requests without having to re-establish a TCP connection.

**Note:** This option can potentially introduce memory leaks. Please check [node-fetch/node-fetch#1325](https://github.com/node-fetch/node-fetch/pull/1325).

## 📦 Bundler Notes

- All targets are exported with Module and CommonJS format and named exports
Expand Down
1 change: 1 addition & 0 deletions build.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ export default defineBuildConfig({
emitCJS: true,
},
entries: ["src/index", "src/node"],
externals: ["undici"],
});
9 changes: 9 additions & 0 deletions examples/proxy.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Agent } from "undici";
import { ofetch } from "ofetch";

// Note: This make fetch unsecure to MITM attacks. USE AT YOUW OWN RISK!
const unsecureAgent = new Agent({ connect: { rejectUnauthorized: false } });
const unsecureFetch = ofetch.create({ dispatcher: unsecureAgent });
const data = await unsecureFetch("https://www.squid-cache.org/");

console.log(data);
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@
"std-env": "^3.7.0",
"typescript": "^5.5.4",
"unbuild": "2.0.0",
"undici": "^5.27.0",
"vitest": "^2.0.5"
},
"packageManager": "[email protected]"
Expand Down
17 changes: 17 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 13 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,22 @@ export interface FetchOptions<R extends ResponseType = ResponseType, T = any>
/**
* @experimental Set to "half" to enable duplex streaming.
* Will be automatically set to "half" when using a ReadableStream as body.
* https://fetch.spec.whatwg.org/#enumdef-requestduplex
* @see https://fetch.spec.whatwg.org/#enumdef-requestduplex
*/
duplex?: "half" | undefined;

/**
* Only supported in Node.js >= 18 using undici
*
* @see https://undici.nodejs.org/#/docs/api/Dispatcher
*/
dispatcher?: InstanceType<typeof import("undici").Dispatcher>;

/**
* Only supported older Node.js versions using node-fetch-native polyfill.
*/
agent?: unknown;

/** timeout in milliseconds */
timeout?: number;

Expand Down

0 comments on commit 6aff08e

Please sign in to comment.