diff --git a/docs/guides/http/fetch-unix.md b/docs/guides/http/fetch-unix.md new file mode 100644 index 0000000000..0f66aeef67 --- /dev/null +++ b/docs/guides/http/fetch-unix.md @@ -0,0 +1,33 @@ +--- +name: Send an HTTP request over a unix domain socket with fetch +--- + +In Bun, the `unix` option in `fetch()` lets you send HTTP requests over a [unix domain socket](https://en.wikipedia.org/wiki/Unix_domain_socket). + +```ts +const unix = "/var/run/docker.sock"; + +const response = await fetch("http://localhost/info", { unix }); + +const body = await response.json(); +console.log(body); // { ... } +``` + +--- + +The `unix` option is a string that specifies the local file path to a unix domain socket. The `fetch()` function will use the socket to send the request to the server instead of using a TCP network connection. `https` is also supported by using the `https://` protocol in the URL instead of `http://`. + +To send a `POST` request to an API endpoint over a unix domain socket: + +```ts +const response = await fetch("https://hostname/a/path", { + unix: "/var/run/path/to/unix.sock", + method: "POST", + body: JSON.stringify({ message: "Hello from Bun!" }), + headers: { + "Content-Type": "application/json", + }, +}); + +const body = await response.json(); +``` diff --git a/src/js/node/http.ts b/src/js/node/http.ts index d1f19bed61..ed03de7075 100644 --- a/src/js/node/http.ts +++ b/src/js/node/http.ts @@ -1440,21 +1440,35 @@ class ClientRequest extends OutgoingMessage { url = `${this.#protocol}//${this.#host}${this.#useDefaultPort ? "" : ":" + this.#port}${this.#path}`; } try { - //@ts-ignore - this.#fetchRequest = fetch(url, { + const fetchOptions: any = { method, headers: this.getHeaders(), body: body && method !== "GET" && method !== "HEAD" && method !== "OPTIONS" ? body : undefined, redirect: "manual", - verbose: !!$debug, signal: this[kAbortController].signal, - proxy: proxy, // Timeouts are handled via this.setTimeout. timeout: false, // Disable auto gzip/deflate decompress: false, - }) + }; + + if (!!$debug) { + fetchOptions.verbose = true; + } + + if (proxy) { + fetchOptions.proxy = proxy; + } + + const socketPath = this.#socketPath; + + if (socketPath) { + fetchOptions.unix = socketPath; + } + + //@ts-ignore + this.#fetchRequest = fetch(url, fetchOptions) .then(response => { const prevIsHTTPS = isNextIncomingMessageHTTPS; isNextIncomingMessageHTTPS = response.url.startsWith("https:"); diff --git a/test/js/web/fetch/fetch.unix.test.ts b/test/js/web/fetch/fetch.unix.test.ts index 5e42a3a3e3..958cfe70a6 100644 --- a/test/js/web/fetch/fetch.unix.test.ts +++ b/test/js/web/fetch/fetch.unix.test.ts @@ -3,7 +3,7 @@ import { afterAll, afterEach, expect, it } from "bun:test"; import { mkdtempSync, realpathSync, rmSync } from "fs"; import { tmpdir } from "os"; import { join } from "path"; - +import { request } from "http"; const tmp_dir = mkdtempSync(join(realpathSync(tmpdir()), "fetch.unix.test")); let server_unix: Server; @@ -49,6 +49,41 @@ it("provide body", async () => { } }); +it("works with node:http", async () => { + const path = startServerUnix({ + fetch(req) { + return new Response(req.body); + }, + }); + + const promises = []; + for (let i = 0; i < 20; i++) { + const { promise, resolve } = Promise.withResolvers(); + const req = request( + { + path: "/hello", + method: "POST", + socketPath: path, + }, + res => { + let data = ""; + res.on("data", chunk => { + data += chunk; + }); + res.on("end", () => { + resolve(data); + }); + }, + ); + + req.write(String(i)); + req.end(); + promises.push(promise.then(data => expect(data).toBe(String(i)))); + } + + await Promise.all(promises); +}); + it("handle redirect to non-unix", async () => { startServer({ async fetch(req) { @@ -69,6 +104,7 @@ it("handle redirect to non-unix", async () => { return new Response(null, { status: 404 }); }, }); + // POST with body for (let i = 0; i < 20; i++) { const response = await fetch("http://localhost/hello", { unix: path });