@lopatnov/express-reverse-proxy 5.0.6 → 5.0.8
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/README.md +102 -85
- package/package.json +1 -1
- package/server.js +24 -3
package/README.md
CHANGED
|
@@ -269,14 +269,14 @@ If `server-config.json` already exists, the command asks before overwriting.
|
|
|
269
269
|
|
|
270
270
|
Manage the PM2 process cluster. Action defaults to `start` when omitted.
|
|
271
271
|
|
|
272
|
-
| Action | Description
|
|
273
|
-
| --------- |
|
|
274
|
-
| `start` | Start the cluster (default when action is omitted)
|
|
275
|
-
| `stop` | Stop all cluster instances
|
|
276
|
-
| `restart` | Restart all cluster instances
|
|
277
|
-
| `status` | Show PM2 process status table
|
|
278
|
-
| `logs` | Stream the last 200 log lines
|
|
279
|
-
| `monitor` | Open the PM2 real-time monitor
|
|
272
|
+
| Action | Description |
|
|
273
|
+
| --------- | -------------------------------------------------- |
|
|
274
|
+
| `start` | Start the cluster (default when action is omitted) |
|
|
275
|
+
| `stop` | Stop all cluster instances |
|
|
276
|
+
| `restart` | Restart all cluster instances |
|
|
277
|
+
| `status` | Show PM2 process status table |
|
|
278
|
+
| `logs` | Stream the last 200 log lines |
|
|
279
|
+
| `monitor` | Open the PM2 real-time monitor |
|
|
280
280
|
|
|
281
281
|
```shell
|
|
282
282
|
express-reverse-proxy --cluster # same as --cluster start
|
|
@@ -325,10 +325,10 @@ Add a `$schema` reference to your config file to get property autocomplete, desc
|
|
|
325
325
|
|
|
326
326
|
### Environment variables
|
|
327
327
|
|
|
328
|
-
| Variable
|
|
329
|
-
|
|
330
|
-
| `PORT`
|
|
331
|
-
| `NODE_ENV` | —
|
|
328
|
+
| Variable | Default | Description |
|
|
329
|
+
| ---------- | ------- | -------------------------------------------------------------- |
|
|
330
|
+
| `PORT` | `8000` | Overrides the port when it is not set in the config file |
|
|
331
|
+
| `NODE_ENV` | — | Passed through to PM2 env profiles (`env` / `env_development`) |
|
|
332
332
|
|
|
333
333
|
### port
|
|
334
334
|
|
|
@@ -360,10 +360,10 @@ Controls HTTP request logging (Morgan). Enabled by default (`dev` format). Set t
|
|
|
360
360
|
}
|
|
361
361
|
```
|
|
362
362
|
|
|
363
|
-
| Option | Default
|
|
364
|
-
| -------- |
|
|
365
|
-
| `format` | `"combined"` | Morgan format: `combined`, `common`, `dev`, `short`, or `tiny` |
|
|
366
|
-
| `file` | none
|
|
363
|
+
| Option | Default | Description |
|
|
364
|
+
| -------- | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
|
|
365
|
+
| `format` | `"dev"` (console) / `"combined"` (file) | Morgan format: `combined`, `common`, `dev`, `short`, or `tiny`. Defaults to `"combined"` when `file` is set, `"dev"` otherwise |
|
|
366
|
+
| `file` | none | Path to log file (relative to config file). Appended if exists |
|
|
367
367
|
|
|
368
368
|
When `file` is set, logs are written to the file only (not to the console).
|
|
369
369
|
|
|
@@ -383,10 +383,10 @@ Watches the `folders` directories for file changes and automatically reloads con
|
|
|
383
383
|
|
|
384
384
|
The server exposes two endpoints when hot reload is enabled:
|
|
385
385
|
|
|
386
|
-
| Endpoint
|
|
387
|
-
|
|
|
388
|
-
| `GET /__hot-reload__`
|
|
389
|
-
| `GET /__hot-reload__/client.js` | Ready-to-use client script
|
|
386
|
+
| Endpoint | Description |
|
|
387
|
+
| ------------------------------- | ------------------------------------ |
|
|
388
|
+
| `GET /__hot-reload__` | SSE stream — browsers subscribe here |
|
|
389
|
+
| `GET /__hot-reload__/client.js` | Ready-to-use client script |
|
|
390
390
|
|
|
391
391
|
#### Connecting the client
|
|
392
392
|
|
|
@@ -399,7 +399,7 @@ The server exposes two endpoints when hot reload is enabled:
|
|
|
399
399
|
**Option B — bundled project** (Vite, webpack, etc.): import the client module. The bundler resolves it through the package `exports` field:
|
|
400
400
|
|
|
401
401
|
```js
|
|
402
|
-
import
|
|
402
|
+
import "@lopatnov/express-reverse-proxy/hot-reload-client";
|
|
403
403
|
```
|
|
404
404
|
|
|
405
405
|
Both options connect to `/__hot-reload__` and call `location.reload()` when a file change is detected. The connection is re-established automatically after 3 seconds if the server restarts.
|
|
@@ -446,11 +446,11 @@ Permanently or temporarily redirect URL paths to new destinations. Redirects are
|
|
|
446
446
|
}
|
|
447
447
|
```
|
|
448
448
|
|
|
449
|
-
| Field | Default | Description
|
|
450
|
-
| -------- | ------- |
|
|
451
|
-
| `from` | — | Source URL path
|
|
452
|
-
| `to` | — | Destination path or full URL
|
|
453
|
-
| `status` | `301` | HTTP redirect status: `301`, `302`, `307`, or `308`
|
|
449
|
+
| Field | Default | Description |
|
|
450
|
+
| -------- | ------- | --------------------------------------------------- |
|
|
451
|
+
| `from` | — | Source URL path _(array form only, required)_ |
|
|
452
|
+
| `to` | — | Destination path or full URL _(required)_ |
|
|
453
|
+
| `status` | `301` | HTTP redirect status: `301`, `302`, `307`, or `308` |
|
|
454
454
|
|
|
455
455
|
> `301` — Moved Permanently. `302` — Found (temporary). Use `301` for permanent URL changes and `302` for temporary ones.
|
|
456
456
|
|
|
@@ -538,7 +538,11 @@ Forward requests to a back-end server. Supports three forms:
|
|
|
538
538
|
```json
|
|
539
539
|
{
|
|
540
540
|
"proxy": {
|
|
541
|
-
"/api": [
|
|
541
|
+
"/api": [
|
|
542
|
+
"http://backend1:3000",
|
|
543
|
+
"http://backend2:3000",
|
|
544
|
+
"http://backend3:3000"
|
|
545
|
+
]
|
|
542
546
|
}
|
|
543
547
|
}
|
|
544
548
|
```
|
|
@@ -629,8 +633,8 @@ Enable HTTPS on a port by adding an `ssl` object to any site config for that por
|
|
|
629
633
|
| ---------- | --------- | -------------------------------------------------------- |
|
|
630
634
|
| `key` | `string` | Path to the private key file (PEM format) |
|
|
631
635
|
| `cert` | `string` | Path to the certificate file (PEM format) |
|
|
632
|
-
| `ca` | `string` |
|
|
633
|
-
| `redirect` | `integer` |
|
|
636
|
+
| `ca` | `string` | _(optional)_ Path to the CA bundle for client validation |
|
|
637
|
+
| `redirect` | `integer` | _(optional)_ HTTP port to redirect (301) to HTTPS |
|
|
634
638
|
|
|
635
639
|
Paths are resolved **relative to the config file**, not the current working directory.
|
|
636
640
|
|
|
@@ -780,11 +784,11 @@ Limit the number of requests a client can make in a time window. Responds with `
|
|
|
780
784
|
}
|
|
781
785
|
```
|
|
782
786
|
|
|
783
|
-
| Option
|
|
784
|
-
|
|
|
785
|
-
| `windowMs`
|
|
786
|
-
| `limit`
|
|
787
|
-
| `message`
|
|
787
|
+
| Option | Default | Description |
|
|
788
|
+
| ---------- | -------- | -------------------------------------- |
|
|
789
|
+
| `windowMs` | `60000` | Time window in milliseconds |
|
|
790
|
+
| `limit` | `5` | Maximum requests per client per window |
|
|
791
|
+
| `message` | built-in | Response body when limit is exceeded |
|
|
788
792
|
|
|
789
793
|
See [express-rate-limit docs](https://express-rate-limit.mintlify.app/reference/configuration) for all options.
|
|
790
794
|
|
|
@@ -805,11 +809,11 @@ Protect the site with HTTP Basic Authentication. All requests must include valid
|
|
|
805
809
|
}
|
|
806
810
|
```
|
|
807
811
|
|
|
808
|
-
| Option | Default | Description
|
|
809
|
-
| ----------- | ------- |
|
|
810
|
-
| `users` | — | Object mapping username → password
|
|
812
|
+
| Option | Default | Description |
|
|
813
|
+
| ----------- | ------- | -------------------------------------------------------------- |
|
|
814
|
+
| `users` | — | Object mapping username → password _(required)_ |
|
|
811
815
|
| `challenge` | `false` | Send `WWW-Authenticate` header to trigger browser login dialog |
|
|
812
|
-
| `realm` | — | Realm string shown in the browser login dialog
|
|
816
|
+
| `realm` | — | Realm string shown in the browser login dialog |
|
|
813
817
|
|
|
814
818
|
See [express-basic-auth docs](https://github.com/LionC/express-basic-auth#options) for all options.
|
|
815
819
|
|
|
@@ -841,8 +845,8 @@ Custom path:
|
|
|
841
845
|
}
|
|
842
846
|
```
|
|
843
847
|
|
|
844
|
-
| Option | Default
|
|
845
|
-
| ------ |
|
|
848
|
+
| Option | Default | Description |
|
|
849
|
+
| ------ | --------------- | ------------------------------- |
|
|
846
850
|
| `path` | `"/__health__"` | URL path of the health endpoint |
|
|
847
851
|
|
|
848
852
|
> The health check endpoint is placed before rate limiting and basic auth — it is always publicly accessible regardless of other authentication settings.
|
|
@@ -867,12 +871,12 @@ Execute server-side scripts using the CGI (Common Gateway Interface) protocol. W
|
|
|
867
871
|
}
|
|
868
872
|
```
|
|
869
873
|
|
|
870
|
-
| Option | Default
|
|
871
|
-
| -------------- |
|
|
872
|
-
| `path` | `"/cgi-bin"`
|
|
873
|
-
| `dir` | `"./cgi-bin"`
|
|
874
|
-
| `extensions` | `[".cgi", ".pl", ".py", ".sh"]`
|
|
875
|
-
| `interpreters` | `{}`
|
|
874
|
+
| Option | Default | Description |
|
|
875
|
+
| -------------- | ------------------------------- | --------------------------------------------------------------------- |
|
|
876
|
+
| `path` | `"/cgi-bin"` | URL prefix that triggers CGI dispatch |
|
|
877
|
+
| `dir` | `"./cgi-bin"` | Local directory containing scripts (resolved relative to config file) |
|
|
878
|
+
| `extensions` | `[".cgi", ".pl", ".py", ".sh"]` | File extensions treated as executable CGI scripts |
|
|
879
|
+
| `interpreters` | `{}` | Map of file extension → interpreter command |
|
|
876
880
|
|
|
877
881
|
Shorthand — point directly to the script directory (all defaults apply):
|
|
878
882
|
|
|
@@ -884,18 +888,18 @@ Shorthand — point directly to the script directory (all defaults apply):
|
|
|
884
888
|
|
|
885
889
|
CGI environment variables set for every request:
|
|
886
890
|
|
|
887
|
-
| Variable | Value
|
|
888
|
-
| ----------------- |
|
|
889
|
-
| `REQUEST_METHOD` | HTTP method (`GET`, `POST`, …)
|
|
890
|
-
| `QUERY_STRING` | URL query string (without `?`)
|
|
891
|
-
| `CONTENT_TYPE` | `Content-Type` request header
|
|
892
|
-
| `CONTENT_LENGTH` | `Content-Length` request header
|
|
893
|
-
| `SCRIPT_FILENAME` | Absolute path to the script file
|
|
894
|
-
| `SCRIPT_NAME` | URL path to the script (e.g. `/cgi-bin/hello.py`)
|
|
895
|
-
| `SERVER_NAME` | Requested hostname
|
|
896
|
-
| `SERVER_PORT` | Server listen port
|
|
897
|
-
| `REMOTE_ADDR` | Client IP address
|
|
898
|
-
| `HTTP_*` | All request headers (e.g. `HTTP_ACCEPT`, `HTTP_HOST`)
|
|
891
|
+
| Variable | Value |
|
|
892
|
+
| ----------------- | ----------------------------------------------------- |
|
|
893
|
+
| `REQUEST_METHOD` | HTTP method (`GET`, `POST`, …) |
|
|
894
|
+
| `QUERY_STRING` | URL query string (without `?`) |
|
|
895
|
+
| `CONTENT_TYPE` | `Content-Type` request header |
|
|
896
|
+
| `CONTENT_LENGTH` | `Content-Length` request header |
|
|
897
|
+
| `SCRIPT_FILENAME` | Absolute path to the script file |
|
|
898
|
+
| `SCRIPT_NAME` | URL path to the script (e.g. `/cgi-bin/hello.py`) |
|
|
899
|
+
| `SERVER_NAME` | Requested hostname |
|
|
900
|
+
| `SERVER_PORT` | Server listen port |
|
|
901
|
+
| `REMOTE_ADDR` | Client IP address |
|
|
902
|
+
| `HTTP_*` | All request headers (e.g. `HTTP_ACCEPT`, `HTTP_HOST`) |
|
|
899
903
|
|
|
900
904
|
A minimal Python example (`cgi-bin/hello.py`):
|
|
901
905
|
|
|
@@ -960,39 +964,52 @@ Shorthand — directory only (all defaults apply):
|
|
|
960
964
|
}
|
|
961
965
|
```
|
|
962
966
|
|
|
963
|
-
| Option
|
|
964
|
-
|
|
|
965
|
-
| `path`
|
|
966
|
-
| `dir`
|
|
967
|
-
| `maxFileSize`
|
|
968
|
-
| `maxFiles`
|
|
969
|
-
| `allowedTypes`
|
|
970
|
-
| `fieldName`
|
|
967
|
+
| Option | Default | Description |
|
|
968
|
+
| -------------- | ------------- | ------------------------------------------------------------------------- |
|
|
969
|
+
| `path` | `"/upload"` | URL prefix for the upload endpoint |
|
|
970
|
+
| `dir` | `"./uploads"` | Save directory (resolved relative to the config file) |
|
|
971
|
+
| `maxFileSize` | none | Maximum file size in bytes; responds with `413` when exceeded |
|
|
972
|
+
| `maxFiles` | none | Maximum number of files per request; responds with `400` when exceeded |
|
|
973
|
+
| `allowedTypes` | none | MIME type whitelist; responds with `400` when the type is not in the list |
|
|
974
|
+
| `fieldName` | any field | Accept only files uploaded in this specific form field |
|
|
971
975
|
|
|
972
976
|
**Array form** — multiple upload endpoints on the same site:
|
|
973
977
|
|
|
974
978
|
```json
|
|
975
979
|
{
|
|
976
980
|
"upload": [
|
|
977
|
-
{
|
|
978
|
-
|
|
981
|
+
{
|
|
982
|
+
"path": "/photos",
|
|
983
|
+
"dir": "./photos",
|
|
984
|
+
"allowedTypes": ["image/jpeg", "image/png"]
|
|
985
|
+
},
|
|
986
|
+
{
|
|
987
|
+
"path": "/docs",
|
|
988
|
+
"dir": "./documents",
|
|
989
|
+
"allowedTypes": ["application/pdf"],
|
|
990
|
+
"maxFileSize": 5242880
|
|
991
|
+
}
|
|
979
992
|
]
|
|
980
993
|
}
|
|
981
994
|
```
|
|
982
995
|
|
|
983
996
|
**HTTP interface:**
|
|
984
997
|
|
|
985
|
-
| Method | URL
|
|
986
|
-
| ------ |
|
|
987
|
-
| `POST` | `<path>`
|
|
988
|
-
| `GET` | `<path>/<name>`
|
|
998
|
+
| Method | URL | Description |
|
|
999
|
+
| ------ | --------------- | -------------------------------------- |
|
|
1000
|
+
| `POST` | `<path>` | Upload files via `multipart/form-data` |
|
|
1001
|
+
| `GET` | `<path>/<name>` | Retrieve a previously uploaded file |
|
|
989
1002
|
|
|
990
1003
|
`POST` success response (`200`):
|
|
991
1004
|
|
|
992
1005
|
```json
|
|
993
1006
|
{
|
|
994
1007
|
"files": [
|
|
995
|
-
{
|
|
1008
|
+
{
|
|
1009
|
+
"file": "photo-1700000000000-123456789.jpg",
|
|
1010
|
+
"size": 45678,
|
|
1011
|
+
"originalName": "photo.jpg"
|
|
1012
|
+
}
|
|
996
1013
|
]
|
|
997
1014
|
}
|
|
998
1015
|
```
|
|
@@ -1186,9 +1203,9 @@ Redirect old URLs to new ones after a site restructure, without breaking existin
|
|
|
1186
1203
|
{
|
|
1187
1204
|
"port": 8080,
|
|
1188
1205
|
"redirects": [
|
|
1189
|
-
{ "from": "/about.html",
|
|
1190
|
-
{ "from": "/products.html", "to": "/products",
|
|
1191
|
-
{ "from": "/blog/:slug",
|
|
1206
|
+
{ "from": "/about.html", "to": "/about", "status": 301 },
|
|
1207
|
+
{ "from": "/products.html", "to": "/products", "status": 301 },
|
|
1208
|
+
{ "from": "/blog/:slug", "to": "/posts/:slug", "status": 301 }
|
|
1192
1209
|
],
|
|
1193
1210
|
"folders": "./public"
|
|
1194
1211
|
}
|
|
@@ -1199,7 +1216,7 @@ Or as an object map for simple path-to-path redirects:
|
|
|
1199
1216
|
```json
|
|
1200
1217
|
{
|
|
1201
1218
|
"redirects": {
|
|
1202
|
-
"/old-home":
|
|
1219
|
+
"/old-home": "/",
|
|
1203
1220
|
"/old-about": "/about",
|
|
1204
1221
|
"/legacy-api": "https://api.example.com"
|
|
1205
1222
|
}
|
|
@@ -1386,14 +1403,14 @@ express-reverse-proxy --cluster start --cluster-config ./my-ecosystem.config.cjs
|
|
|
1386
1403
|
|
|
1387
1404
|
Run via npm scripts:
|
|
1388
1405
|
|
|
1389
|
-
| Script
|
|
1390
|
-
|
|
|
1391
|
-
| `npm run pm2-start`
|
|
1392
|
-
| `npm run pm2-restart`
|
|
1393
|
-
| `npm run pm2-stop`
|
|
1394
|
-
| `npm run pm2-status`
|
|
1395
|
-
| `npm run pm2-logs`
|
|
1396
|
-
| `npm run pm2-monitor`
|
|
1406
|
+
| Script | Description |
|
|
1407
|
+
| --------------------- | ------------------------------------------------------------------ |
|
|
1408
|
+
| `npm run pm2-start` | Start cluster (max CPU cores); reads `server-config.json` from cwd |
|
|
1409
|
+
| `npm run pm2-restart` | Restart all instances |
|
|
1410
|
+
| `npm run pm2-stop` | Stop all instances |
|
|
1411
|
+
| `npm run pm2-status` | Show process status |
|
|
1412
|
+
| `npm run pm2-logs` | Show last 200 log lines |
|
|
1413
|
+
| `npm run pm2-monitor` | Open real-time monitor |
|
|
1397
1414
|
|
|
1398
1415
|
Or use the CLI directly:
|
|
1399
1416
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lopatnov/express-reverse-proxy",
|
|
3
3
|
"email": "oleksandr@lopatnov.cv.ua",
|
|
4
|
-
"version": "5.0.
|
|
4
|
+
"version": "5.0.8",
|
|
5
5
|
"description": "Node.js CLI tool to serve static front-end files with a reverse proxy for back-end APIs",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"author": "lopatnov",
|
package/server.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawn, spawnSync } from 'node:child_process';
|
|
3
|
+
import { randomInt } from 'node:crypto';
|
|
3
4
|
import fs from 'node:fs';
|
|
4
5
|
import https from 'node:https';
|
|
5
6
|
import path from 'node:path';
|
|
@@ -316,7 +317,15 @@ function addMappedProxy(router, port, localRootPath, pathPairs) {
|
|
|
316
317
|
|
|
317
318
|
function addProxies(router, port, localRootPath, proxies) {
|
|
318
319
|
proxies.forEach((proxyUrl) => {
|
|
319
|
-
|
|
320
|
+
if (
|
|
321
|
+
Array.isArray(proxyUrl) &&
|
|
322
|
+
proxyUrl.length > 0 &&
|
|
323
|
+
proxyUrl.every((i) => typeof i === 'string')
|
|
324
|
+
) {
|
|
325
|
+
addRemoteProxy(router, port, localRootPath, proxyUrl);
|
|
326
|
+
} else {
|
|
327
|
+
addProxy(router, port, localRootPath, proxyUrl);
|
|
328
|
+
}
|
|
320
329
|
});
|
|
321
330
|
}
|
|
322
331
|
|
|
@@ -535,7 +544,7 @@ function setupUpload(router, siteConfig, configDir) {
|
|
|
535
544
|
const ext = path.extname(file.originalname);
|
|
536
545
|
const base =
|
|
537
546
|
path.basename(file.originalname, ext).replaceAll(/[^a-zA-Z0-9_.-]/g, '_') || 'file';
|
|
538
|
-
cb(null, `${base}-${Date.now()}-${
|
|
547
|
+
cb(null, `${base}-${Date.now()}-${randomInt(0, 1_000_000_000)}${ext}`);
|
|
539
548
|
},
|
|
540
549
|
});
|
|
541
550
|
|
|
@@ -548,7 +557,19 @@ function setupUpload(router, siteConfig, configDir) {
|
|
|
548
557
|
? uploader.array(uploadConfig.fieldName)
|
|
549
558
|
: uploader.any();
|
|
550
559
|
|
|
551
|
-
router.post(uploadUrlPath,
|
|
560
|
+
router.post(uploadUrlPath, (req, res, next) => {
|
|
561
|
+
multerMiddleware(req, res, (err) => {
|
|
562
|
+
if (err instanceof multer.MulterError) {
|
|
563
|
+
const status = err.code === 'LIMIT_FILE_SIZE' ? 413 : 400;
|
|
564
|
+
return res.status(status).json({ error: err.message });
|
|
565
|
+
}
|
|
566
|
+
if (err?.status || err?.statusCode) {
|
|
567
|
+
return res.status(err.statusCode || err.status).json({ error: err.message });
|
|
568
|
+
}
|
|
569
|
+
if (err) return next(err);
|
|
570
|
+
return handleUploadResponse(req, res);
|
|
571
|
+
});
|
|
572
|
+
});
|
|
552
573
|
router.use(uploadUrlPath, express.static(uploadDir));
|
|
553
574
|
console.log(`[upload] POST ${uploadUrlPath} → ${uploadDir}`);
|
|
554
575
|
}
|