portless 0.3.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-Y5OVKUR4.js → chunk-VRBD6YAY.js} +88 -15
- package/dist/cli.js +643 -69
- package/dist/index.d.ts +22 -5
- package/dist/index.js +1 -1
- package/package.json +11 -30
- package/README.md +0 -131
|
@@ -5,8 +5,10 @@ function isErrnoException(err) {
|
|
|
5
5
|
function escapeHtml(str) {
|
|
6
6
|
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
7
7
|
}
|
|
8
|
-
function formatUrl(hostname, proxyPort) {
|
|
9
|
-
|
|
8
|
+
function formatUrl(hostname, proxyPort, tls = false) {
|
|
9
|
+
const proto = tls ? "https" : "http";
|
|
10
|
+
const defaultPort = tls ? 443 : 80;
|
|
11
|
+
return proxyPort === defaultPort ? `${proto}://${hostname}` : `${proto}://${hostname}:${proxyPort}`;
|
|
10
12
|
}
|
|
11
13
|
function parseHostname(input) {
|
|
12
14
|
let hostname = input.trim().replace(/^https?:\/\//, "").split("/")[0].toLowerCase();
|
|
@@ -30,24 +32,40 @@ function parseHostname(input) {
|
|
|
30
32
|
|
|
31
33
|
// src/proxy.ts
|
|
32
34
|
import * as http from "http";
|
|
35
|
+
import * as http2 from "http2";
|
|
36
|
+
import * as net from "net";
|
|
33
37
|
var PORTLESS_HEADER = "X-Portless";
|
|
34
|
-
|
|
38
|
+
var HOP_BY_HOP_HEADERS = /* @__PURE__ */ new Set([
|
|
39
|
+
"connection",
|
|
40
|
+
"keep-alive",
|
|
41
|
+
"proxy-connection",
|
|
42
|
+
"transfer-encoding",
|
|
43
|
+
"upgrade"
|
|
44
|
+
]);
|
|
45
|
+
function getRequestHost(req) {
|
|
46
|
+
const authority = req.headers[":authority"];
|
|
47
|
+
if (typeof authority === "string" && authority) return authority;
|
|
48
|
+
return req.headers.host || "";
|
|
49
|
+
}
|
|
50
|
+
function buildForwardedHeaders(req, tls) {
|
|
35
51
|
const headers = {};
|
|
36
52
|
const remoteAddress = req.socket.remoteAddress || "127.0.0.1";
|
|
37
|
-
const proto = "http";
|
|
38
|
-
const
|
|
53
|
+
const proto = tls ? "https" : "http";
|
|
54
|
+
const defaultPort = tls ? "443" : "80";
|
|
55
|
+
const hostHeader = getRequestHost(req);
|
|
39
56
|
headers["x-forwarded-for"] = req.headers["x-forwarded-for"] ? `${req.headers["x-forwarded-for"]}, ${remoteAddress}` : remoteAddress;
|
|
40
57
|
headers["x-forwarded-proto"] = req.headers["x-forwarded-proto"] || proto;
|
|
41
58
|
headers["x-forwarded-host"] = req.headers["x-forwarded-host"] || hostHeader;
|
|
42
|
-
headers["x-forwarded-port"] = req.headers["x-forwarded-port"] || hostHeader.split(":")[1] ||
|
|
59
|
+
headers["x-forwarded-port"] = req.headers["x-forwarded-port"] || hostHeader.split(":")[1] || defaultPort;
|
|
43
60
|
return headers;
|
|
44
61
|
}
|
|
45
62
|
function createProxyServer(options) {
|
|
46
|
-
const { getRoutes, proxyPort, onError = (msg) => console.error(msg) } = options;
|
|
63
|
+
const { getRoutes, proxyPort, onError = (msg) => console.error(msg), tls } = options;
|
|
64
|
+
const isTls = !!tls;
|
|
47
65
|
const handleRequest = (req, res) => {
|
|
48
66
|
res.setHeader(PORTLESS_HEADER, "1");
|
|
49
67
|
const routes = getRoutes();
|
|
50
|
-
const host = (req
|
|
68
|
+
const host = getRequestHost(req).split(":")[0];
|
|
51
69
|
if (!host) {
|
|
52
70
|
res.writeHead(400, { "Content-Type": "text/plain" });
|
|
53
71
|
res.end("Missing Host header");
|
|
@@ -66,7 +84,7 @@ function createProxyServer(options) {
|
|
|
66
84
|
${routes.length > 0 ? `
|
|
67
85
|
<h2>Active apps:</h2>
|
|
68
86
|
<ul>
|
|
69
|
-
${routes.map((r) => `<li><a href="${escapeHtml(formatUrl(r.hostname, proxyPort))}">${escapeHtml(r.hostname)}</a> - localhost:${escapeHtml(String(r.port))}</li>`).join("")}
|
|
87
|
+
${routes.map((r) => `<li><a href="${escapeHtml(formatUrl(r.hostname, proxyPort, isTls))}">${escapeHtml(r.hostname)}</a> - localhost:${escapeHtml(String(r.port))}</li>`).join("")}
|
|
70
88
|
</ul>
|
|
71
89
|
` : "<p><em>No apps running.</em></p>"}
|
|
72
90
|
<p>Start an app with: <code>portless ${safeHost.replace(".localhost", "")} your-command</code></p>
|
|
@@ -75,11 +93,16 @@ function createProxyServer(options) {
|
|
|
75
93
|
`);
|
|
76
94
|
return;
|
|
77
95
|
}
|
|
78
|
-
const forwardedHeaders = buildForwardedHeaders(req);
|
|
96
|
+
const forwardedHeaders = buildForwardedHeaders(req, isTls);
|
|
79
97
|
const proxyReqHeaders = { ...req.headers };
|
|
80
98
|
for (const [key, value] of Object.entries(forwardedHeaders)) {
|
|
81
99
|
proxyReqHeaders[key] = value;
|
|
82
100
|
}
|
|
101
|
+
for (const key of Object.keys(proxyReqHeaders)) {
|
|
102
|
+
if (key.startsWith(":")) {
|
|
103
|
+
delete proxyReqHeaders[key];
|
|
104
|
+
}
|
|
105
|
+
}
|
|
83
106
|
const proxyReq = http.request(
|
|
84
107
|
{
|
|
85
108
|
hostname: "127.0.0.1",
|
|
@@ -89,12 +112,18 @@ function createProxyServer(options) {
|
|
|
89
112
|
headers: proxyReqHeaders
|
|
90
113
|
},
|
|
91
114
|
(proxyRes) => {
|
|
92
|
-
|
|
115
|
+
const responseHeaders = { ...proxyRes.headers };
|
|
116
|
+
if (isTls) {
|
|
117
|
+
for (const h of HOP_BY_HOP_HEADERS) {
|
|
118
|
+
delete responseHeaders[h];
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
res.writeHead(proxyRes.statusCode || 502, responseHeaders);
|
|
93
122
|
proxyRes.pipe(res);
|
|
94
123
|
}
|
|
95
124
|
);
|
|
96
125
|
proxyReq.on("error", (err) => {
|
|
97
|
-
onError(`Proxy error for ${req
|
|
126
|
+
onError(`Proxy error for ${getRequestHost(req)}: ${err.message}`);
|
|
98
127
|
if (!res.headersSent) {
|
|
99
128
|
const errWithCode = err;
|
|
100
129
|
const message = errWithCode.code === "ECONNREFUSED" ? "Bad Gateway: the target app is not responding. It may have crashed." : "Bad Gateway: the target app may not be running.";
|
|
@@ -116,17 +145,22 @@ function createProxyServer(options) {
|
|
|
116
145
|
};
|
|
117
146
|
const handleUpgrade = (req, socket, head) => {
|
|
118
147
|
const routes = getRoutes();
|
|
119
|
-
const host = (req
|
|
148
|
+
const host = getRequestHost(req).split(":")[0];
|
|
120
149
|
const route = routes.find((r) => r.hostname === host);
|
|
121
150
|
if (!route) {
|
|
122
151
|
socket.destroy();
|
|
123
152
|
return;
|
|
124
153
|
}
|
|
125
|
-
const forwardedHeaders = buildForwardedHeaders(req);
|
|
154
|
+
const forwardedHeaders = buildForwardedHeaders(req, isTls);
|
|
126
155
|
const proxyReqHeaders = { ...req.headers };
|
|
127
156
|
for (const [key, value] of Object.entries(forwardedHeaders)) {
|
|
128
157
|
proxyReqHeaders[key] = value;
|
|
129
158
|
}
|
|
159
|
+
for (const key of Object.keys(proxyReqHeaders)) {
|
|
160
|
+
if (key.startsWith(":")) {
|
|
161
|
+
delete proxyReqHeaders[key];
|
|
162
|
+
}
|
|
163
|
+
}
|
|
130
164
|
const proxyReq = http.request({
|
|
131
165
|
hostname: "127.0.0.1",
|
|
132
166
|
port: route.port,
|
|
@@ -152,7 +186,7 @@ function createProxyServer(options) {
|
|
|
152
186
|
socket.on("error", () => proxySocket.destroy());
|
|
153
187
|
});
|
|
154
188
|
proxyReq.on("error", (err) => {
|
|
155
|
-
onError(`WebSocket proxy error for ${req
|
|
189
|
+
onError(`WebSocket proxy error for ${getRequestHost(req)}: ${err.message}`);
|
|
156
190
|
socket.destroy();
|
|
157
191
|
});
|
|
158
192
|
proxyReq.on("response", (res) => {
|
|
@@ -173,6 +207,44 @@ function createProxyServer(options) {
|
|
|
173
207
|
}
|
|
174
208
|
proxyReq.end();
|
|
175
209
|
};
|
|
210
|
+
if (tls) {
|
|
211
|
+
const h2Server = http2.createSecureServer({
|
|
212
|
+
cert: tls.cert,
|
|
213
|
+
key: tls.key,
|
|
214
|
+
allowHTTP1: true,
|
|
215
|
+
...tls.SNICallback ? { SNICallback: tls.SNICallback } : {}
|
|
216
|
+
});
|
|
217
|
+
h2Server.on("request", (req, res) => {
|
|
218
|
+
handleRequest(req, res);
|
|
219
|
+
});
|
|
220
|
+
h2Server.on("upgrade", (req, socket, head) => {
|
|
221
|
+
handleUpgrade(req, socket, head);
|
|
222
|
+
});
|
|
223
|
+
const plainServer = http.createServer(handleRequest);
|
|
224
|
+
plainServer.on("upgrade", handleUpgrade);
|
|
225
|
+
const wrapper = net.createServer((socket) => {
|
|
226
|
+
socket.once("readable", () => {
|
|
227
|
+
const buf = socket.read(1);
|
|
228
|
+
if (!buf) {
|
|
229
|
+
socket.destroy();
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
socket.unshift(buf);
|
|
233
|
+
if (buf[0] === 22) {
|
|
234
|
+
h2Server.emit("connection", socket);
|
|
235
|
+
} else {
|
|
236
|
+
plainServer.emit("connection", socket);
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
const origClose = wrapper.close.bind(wrapper);
|
|
241
|
+
wrapper.close = function(cb) {
|
|
242
|
+
h2Server.close();
|
|
243
|
+
plainServer.close();
|
|
244
|
+
return origClose(cb);
|
|
245
|
+
};
|
|
246
|
+
return wrapper;
|
|
247
|
+
}
|
|
176
248
|
const httpServer = http.createServer(handleRequest);
|
|
177
249
|
httpServer.on("upgrade", handleUpgrade);
|
|
178
250
|
return httpServer;
|
|
@@ -190,6 +262,7 @@ function isValidRoute(value) {
|
|
|
190
262
|
return typeof value === "object" && value !== null && typeof value.hostname === "string" && typeof value.port === "number" && typeof value.pid === "number";
|
|
191
263
|
}
|
|
192
264
|
var RouteStore = class _RouteStore {
|
|
265
|
+
/** The state directory path. */
|
|
193
266
|
dir;
|
|
194
267
|
routesPath;
|
|
195
268
|
lockPath;
|