Support socketPath in node:http request (#9284)

* Support `socketPath` in node:http request

* fix dashes

---------

Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
Co-authored-by: dave caruso <me@paperdave.net>
This commit is contained in:
Jarred Sumner
2024-03-06 15:28:35 -08:00
committed by GitHub
parent a66926243d
commit f6d5325daa
3 changed files with 89 additions and 6 deletions

View File

@@ -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();
```

View File

@@ -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:");

View File

@@ -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<string>();
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 });