6989a98d75
- Arkitektur: docs/auth/passwordless-architecture.md - Backend: iom/quixzoom-auth-service/ (FastAPI + Redis) - Webb: quixzoom-market-pages/se/login/ (QR-kod + polling) - App: iom/quixzoom-app/src/features/auth/ (push + deep links) Flöde: QR-kod → app-godkännande → webb-inloggad
264 lines
12 KiB
Markdown
264 lines
12 KiB
Markdown
# 🔀 httpxy
|
|
|
|
[![npm version][npm-version-src]][npm-version-href]
|
|
[![npm downloads][npm-downloads-src]][npm-downloads-href]
|
|
[![bundle][bundle-src]][bundle-href]
|
|
[![Codecov][codecov-src]][codecov-href]
|
|
|
|
A Full-Featured HTTP and WebSocket Proxy for Node.js
|
|
|
|
## Proxy Fetch
|
|
|
|
`proxyFetch` is a proxy utility with web standard (`Request`/`Response`) interfaces. It forwards requests to a specific server address (TCP host/port or Unix socket), bypassing the URL's hostname.
|
|
|
|
```ts
|
|
import { proxyFetch } from "httpxy";
|
|
|
|
// TCP — using a URL string
|
|
const res = await proxyFetch("http://127.0.0.1:3000", "http://example.com/api/data");
|
|
console.log(await res.json());
|
|
|
|
// Unix socket — using a URL string
|
|
const res2 = await proxyFetch("unix:/tmp/app.sock", "http://localhost/health");
|
|
console.log(await res2.text());
|
|
|
|
// Or use an object for more control
|
|
const res3 = await proxyFetch({ host: "127.0.0.1", port: 3000 }, "http://example.com/api/data");
|
|
|
|
// Using a Request object
|
|
const req = new Request("http://example.com/api/data", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ key: "value" }),
|
|
});
|
|
const res4 = await proxyFetch("http://127.0.0.1:3000", req);
|
|
|
|
// Using a URL string with RequestInit
|
|
const res5 = await proxyFetch("http://127.0.0.1:3000", "http://example.com/api/data", {
|
|
method: "PUT",
|
|
headers: { Authorization: "Bearer token" },
|
|
body: JSON.stringify({ updated: true }),
|
|
});
|
|
```
|
|
|
|
It accepts the same `input` and `init` arguments as the global `fetch`, including `Request` objects and streaming bodies, and returns a standard `Response`. Redirects are handled manually by default.
|
|
|
|
## Proxy Upgrade
|
|
|
|
`proxyUpgrade` is a standalone WebSocket upgrade proxy. It forwards `upgrade` requests to a target server without needing a `ProxyServer` instance — the WebSocket counterpart to `proxyFetch`.
|
|
|
|
```ts
|
|
import { createServer } from "node:http";
|
|
import { proxyUpgrade } from "httpxy";
|
|
|
|
const server = createServer((req, res) => {
|
|
// Handle regular HTTP requests...
|
|
});
|
|
|
|
server.on("upgrade", (req, socket, head) => {
|
|
proxyUpgrade("http://127.0.0.1:8080", req, socket, head);
|
|
});
|
|
|
|
server.listen(3000);
|
|
```
|
|
|
|
It accepts the same `addr` formats as `proxyFetch` (`"http://host:port"`, `"unix:/path"`, or `{ host, port }` / `{ socketPath }`), and returns a `Promise<Socket>` that resolves with the upstream proxy socket once the WebSocket connection is established.
|
|
|
|
```ts
|
|
// With options
|
|
server.on("upgrade", (req, socket, head) => {
|
|
proxyUpgrade({ host: "127.0.0.1", port: 8080 }, req, socket, head, {
|
|
// changeOrigin: true, // rewrite Host header
|
|
// xfwd: false, // disable x-forwarded-* headers (enabled by default)
|
|
});
|
|
});
|
|
```
|
|
|
|
## Proxy Server
|
|
|
|
> [!NOTE]
|
|
> Proxy server was originally forked from [http-party/node-http-proxy](https://github.com/http-party/node-http-proxy).
|
|
|
|
Create proxy:
|
|
|
|
```ts
|
|
import { createServer } from "node:http";
|
|
import { createProxyServer } from "httpxy";
|
|
|
|
const proxy = createProxyServer({});
|
|
|
|
const server = createServer(async (req, res) => {
|
|
try {
|
|
await proxy.web(req, res, {
|
|
target: address /* address of your proxy server here */,
|
|
});
|
|
} catch (error) {
|
|
console.error(error);
|
|
res.statusCode = 500;
|
|
res.end("Proxy error: " + error.toString());
|
|
}
|
|
});
|
|
|
|
server.listen(3000, () => {
|
|
console.log("Proxy is listening on http://localhost:3000");
|
|
});
|
|
```
|
|
|
|
## Options
|
|
|
|
| Option | Type | Default | Description |
|
|
| ----------------------- | -------------------------------------- | ---------- | --------------------------------------------------------------------------- |
|
|
| `target` | `string \| URL \| ProxyTargetDetailed` | — | Target server URL |
|
|
| `forward` | `string \| URL` | — | Forward server URL (pipes request without the target's response) |
|
|
| `agent` | `http.Agent \| false` | keep-alive | Shared keep-alive agent by default. Set `false` to disable connection reuse |
|
|
| `ssl` | `https.ServerOptions` | — | Object passed to `https.createServer()` |
|
|
| `ws` | `boolean` | `false` | Enable WebSocket proxying |
|
|
| `xfwd` | `boolean` | `false` | Add `x-forwarded-*` headers |
|
|
| `secure` | `boolean` | — | Verify SSL certificates |
|
|
| `toProxy` | `boolean` | `false` | Pass absolute URL as path (proxy-to-proxy) |
|
|
| `prependPath` | `boolean` | `true` | Prepend the target's path to the proxy path |
|
|
| `ignorePath` | `boolean` | `false` | Ignore the incoming request path |
|
|
| `localAddress` | `string` | — | Local interface to bind for outgoing connections |
|
|
| `changeOrigin` | `boolean` | `false` | Change the `Host` header to match the target URL |
|
|
| `preserveHeaderKeyCase` | `boolean` | `false` | Keep original letter case of response header keys |
|
|
| `auth` | `string` | — | Basic authentication (`'user:password'`) for `Authorization` header |
|
|
| `hostRewrite` | `string` | — | Rewrite the `Location` hostname on redirects (301/302/307/308) |
|
|
| `autoRewrite` | `boolean` | `false` | Rewrite `Location` host/port on redirects based on the request |
|
|
| `protocolRewrite` | `string` | — | Rewrite `Location` protocol on redirects (`'http'` or `'https'`) |
|
|
| `cookieDomainRewrite` | `false \| string \| object` | `false` | Rewrite domain of `Set-Cookie` headers |
|
|
| `cookiePathRewrite` | `false \| string \| object` | `false` | Rewrite path of `Set-Cookie` headers |
|
|
| `headers` | `object` | — | Extra headers to add to target requests |
|
|
| `proxyTimeout` | `number` | `120000` | Timeout (ms) for the proxy request to the target |
|
|
| `timeout` | `number` | — | Timeout (ms) for the incoming request |
|
|
| `selfHandleResponse` | `boolean` | `false` | Disable automatic response piping (handle `proxyRes` yourself) |
|
|
| `followRedirects` | `boolean \| number` | `false` | Follow HTTP redirects from target. `true` = max 5 hops; number = custom max |
|
|
| `buffer` | `stream.Stream` | — | Stream to use as request body instead of the incoming request |
|
|
|
|
## Events
|
|
|
|
| Event | Arguments | Description |
|
|
| ------------ | ---------------------------------------- | ------------------------------------------------------ |
|
|
| `error` | `(err, req, res, target)` | An error occurred during proxying |
|
|
| `proxyReq` | `(proxyReq, req, res, options)` | Before request is sent to target (modify headers here) |
|
|
| `proxyRes` | `(proxyRes, req, res)` | Response received from target |
|
|
| `proxyReqWs` | `(proxyReq, req, socket, options, head)` | Before WebSocket upgrade request is sent |
|
|
| `open` | `(proxySocket)` | WebSocket connection opened |
|
|
| `close` | `(proxyRes, proxySocket, proxyHead)` | WebSocket connection closed |
|
|
| `start` | `(req, res, target)` | Proxy processing started |
|
|
| `end` | `(req, res, proxyRes)` | Proxy request completed |
|
|
|
|
## Examples
|
|
|
|
### HTTP Proxy
|
|
|
|
```ts
|
|
import { createServer } from "node:http";
|
|
import { createProxyServer } from "httpxy";
|
|
|
|
const proxy = createProxyServer({});
|
|
|
|
const server = createServer(async (req, res) => {
|
|
await proxy.web(req, res, { target: "http://localhost:8080" });
|
|
});
|
|
|
|
server.listen(3000);
|
|
```
|
|
|
|
### WebSocket Proxy
|
|
|
|
```ts
|
|
import { createServer } from "node:http";
|
|
import { createProxyServer } from "httpxy";
|
|
|
|
const proxy = createProxyServer({ target: "http://localhost:8080", ws: true });
|
|
|
|
const server = createServer(async (req, res) => {
|
|
await proxy.web(req, res);
|
|
});
|
|
|
|
server.on("upgrade", (req, socket, head) => {
|
|
proxy.ws(req, socket, { target: "http://localhost:8080" }, head);
|
|
});
|
|
|
|
server.listen(3000);
|
|
```
|
|
|
|
### Modify Request Headers
|
|
|
|
```ts
|
|
import { createServer } from "node:http";
|
|
import { createProxyServer } from "httpxy";
|
|
|
|
const proxy = createProxyServer({ target: "http://localhost:8080" });
|
|
|
|
proxy.on("proxyReq", (proxyReq) => {
|
|
proxyReq.setHeader("X-Forwarded-By", "httpxy");
|
|
});
|
|
|
|
const server = createServer(async (req, res) => {
|
|
await proxy.web(req, res);
|
|
});
|
|
|
|
server.listen(3000);
|
|
```
|
|
|
|
### HTTPS Proxy
|
|
|
|
```ts
|
|
import { readFileSync } from "node:fs";
|
|
import { createProxyServer } from "httpxy";
|
|
|
|
const proxy = createProxyServer({
|
|
ssl: {
|
|
key: readFileSync("server-key.pem", "utf8"),
|
|
cert: readFileSync("server-cert.pem", "utf8"),
|
|
},
|
|
target: "https://localhost:8443",
|
|
secure: false, // allow self-signed certificates
|
|
});
|
|
|
|
proxy.listen(3000);
|
|
```
|
|
|
|
### Standalone Proxy Server
|
|
|
|
```ts
|
|
import { createProxyServer } from "httpxy";
|
|
|
|
const proxy = createProxyServer({
|
|
target: "http://localhost:8080",
|
|
changeOrigin: true,
|
|
});
|
|
|
|
proxy.listen(3000);
|
|
```
|
|
|
|
## Development
|
|
|
|
- Clone this repository
|
|
- Install latest LTS version of [Node.js](https://nodejs.org/en/)
|
|
- Enable [Corepack](https://github.com/nodejs/corepack) using `corepack enable`
|
|
- Install dependencies using `pnpm install`
|
|
- Run interactive tests using `pnpm dev`
|
|
|
|
## Acknowledgements
|
|
|
|
Performance optimizations in httpxy were inspired by analysis of [fast-proxy](https://github.com/fastify/fast-proxy) and [@fastify/http-proxy](https://github.com/fastify/fastify-http-proxy).
|
|
|
|
## License
|
|
|
|
Made with 💛
|
|
|
|
Published under [MIT License](./LICENSE).
|
|
|
|
<!-- Badges -->
|
|
|
|
[npm-version-src]: https://img.shields.io/npm/v/httpxy?style=flat&colorA=18181B&colorB=F0DB4F
|
|
[npm-version-href]: https://npmjs.com/package/httpxy
|
|
[npm-downloads-src]: https://img.shields.io/npm/dm/httpxy?style=flat&colorA=18181B&colorB=F0DB4F
|
|
[npm-downloads-href]: https://npmjs.com/package/httpxy
|
|
[codecov-src]: https://img.shields.io/codecov/c/gh/unjs/httpxy/main?style=flat&colorA=18181B&colorB=F0DB4F
|
|
[codecov-href]: https://codecov.io/gh/unjs/httpxy
|
|
[bundle-src]: https://img.shields.io/bundlephobia/minzip/httpxy?style=flat&colorA=18181B&colorB=F0DB4F
|
|
[bundle-href]: https://bundlephobia.com/result?p=httpxy
|