@robiki/proxy 1.0.0 → 1.0.2
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.de.md +639 -0
- package/README.es.md +639 -0
- package/README.ja.md +639 -0
- package/README.md +55 -4
- package/README.pl.md +639 -0
- package/README.ru.md +639 -0
- package/README.zh.md +639 -0
- package/dist/config-CQ7zIaQt.d.ts +172 -0
- package/dist/config-CeJ1tf8T.d.ts +174 -0
- package/dist/index.d.ts +5 -5
- package/dist/index.js +79 -38
- package/dist/utils/config.d.ts +1 -1
- package/dist/utils/config.js +5 -16
- package/package.json +7 -3
- package/tests/README.md +293 -0
- package/tests/docker/README.md +303 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { OutgoingHttpHeaders, IncomingMessage } from 'node:http';
|
|
2
|
+
import { IncomingHttpHeaders } from 'node:http2';
|
|
3
|
+
import { TLSSocket } from 'node:tls';
|
|
4
|
+
import WebSocket from 'ws';
|
|
5
|
+
|
|
6
|
+
declare enum RequestType {
|
|
7
|
+
API = "api",
|
|
8
|
+
STREAM = "stream",
|
|
9
|
+
WEBSOCKET = "websocket"
|
|
10
|
+
}
|
|
11
|
+
interface TLSWebSocket extends WebSocket {
|
|
12
|
+
_socket: TLSSocket;
|
|
13
|
+
}
|
|
14
|
+
type Router = (req: any, res: any) => void;
|
|
15
|
+
type WebSocketRouter = (req: IncomingMessage, socket: TLSWebSocket, headers: IncomingHttpHeaders) => void;
|
|
16
|
+
type Streamer = (stream: any, headers: any, flags: any) => void;
|
|
17
|
+
interface ForwardValidationResult {
|
|
18
|
+
status: boolean;
|
|
19
|
+
message?: string;
|
|
20
|
+
code?: number;
|
|
21
|
+
headers?: OutgoingHttpHeaders;
|
|
22
|
+
}
|
|
23
|
+
interface ConnectionInfo {
|
|
24
|
+
id: number;
|
|
25
|
+
method: string;
|
|
26
|
+
path: string;
|
|
27
|
+
remoteAddress: string;
|
|
28
|
+
scheme: string;
|
|
29
|
+
authority: string;
|
|
30
|
+
origin: string;
|
|
31
|
+
headers: IncomingHttpHeaders;
|
|
32
|
+
query: URLSearchParams;
|
|
33
|
+
type: RequestType;
|
|
34
|
+
respond: (status: number, headers?: Record<string, string>, body?: string) => void;
|
|
35
|
+
end: (body?: string) => void;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Route configuration for a specific domain/host
|
|
40
|
+
*/
|
|
41
|
+
interface RouteConfig {
|
|
42
|
+
/** Target host:port to proxy to */
|
|
43
|
+
target: string;
|
|
44
|
+
/** Enable SSL/TLS for the target */
|
|
45
|
+
ssl?: boolean;
|
|
46
|
+
/** Remap the URL path before forwarding */
|
|
47
|
+
remap?: (url: string) => string;
|
|
48
|
+
/** Custom CORS configuration */
|
|
49
|
+
cors?: CorsConfig;
|
|
50
|
+
/** Validation function for this route */
|
|
51
|
+
validate?: (info: ConnectionInfo) => Promise<ForwardValidationResult>;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* CORS configuration
|
|
55
|
+
*/
|
|
56
|
+
interface CorsConfig {
|
|
57
|
+
/** Allowed origins (array of strings or '*' for all) */
|
|
58
|
+
origin?: string | string[];
|
|
59
|
+
/** Allowed HTTP methods */
|
|
60
|
+
methods?: string[];
|
|
61
|
+
/** Allowed headers */
|
|
62
|
+
allowedHeaders?: string[];
|
|
63
|
+
/** Exposed headers */
|
|
64
|
+
exposedHeaders?: string[];
|
|
65
|
+
/** Allow credentials */
|
|
66
|
+
credentials?: boolean;
|
|
67
|
+
/** Max age for preflight cache */
|
|
68
|
+
maxAge?: number;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* SSL/TLS certificate configuration
|
|
72
|
+
*/
|
|
73
|
+
interface CertificateConfig {
|
|
74
|
+
/** Path to private key file or key content */
|
|
75
|
+
key: string;
|
|
76
|
+
/** Path to certificate file or cert content */
|
|
77
|
+
cert: string;
|
|
78
|
+
/** Path to CA file or CA content */
|
|
79
|
+
ca?: string;
|
|
80
|
+
/** Allow HTTP/1.1 fallback */
|
|
81
|
+
allowHTTP1?: boolean;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Server configuration
|
|
85
|
+
*/
|
|
86
|
+
interface ServerConfig {
|
|
87
|
+
/** SSL/TLS certificate configuration */
|
|
88
|
+
ssl?: CertificateConfig;
|
|
89
|
+
/** Route configurations mapped by host */
|
|
90
|
+
routes: Record<string, RouteConfig>;
|
|
91
|
+
/** Default CORS configuration */
|
|
92
|
+
cors?: CorsConfig;
|
|
93
|
+
/** Global validation function */
|
|
94
|
+
validate?: (info: ConnectionInfo) => Promise<ForwardValidationResult>;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Proxy configuration manager
|
|
98
|
+
*/
|
|
99
|
+
declare class ProxyConfig {
|
|
100
|
+
private config;
|
|
101
|
+
private sslConfig?;
|
|
102
|
+
constructor(config: ServerConfig);
|
|
103
|
+
/**
|
|
104
|
+
* Initialize SSL configuration
|
|
105
|
+
*/
|
|
106
|
+
private initializeSSL;
|
|
107
|
+
/**
|
|
108
|
+
* Get SSL configuration
|
|
109
|
+
*/
|
|
110
|
+
getSSL(): {
|
|
111
|
+
key: Buffer;
|
|
112
|
+
cert: Buffer;
|
|
113
|
+
ca?: Buffer;
|
|
114
|
+
allowHTTP1?: boolean;
|
|
115
|
+
} | undefined;
|
|
116
|
+
/**
|
|
117
|
+
* Get route configuration for a host
|
|
118
|
+
*/
|
|
119
|
+
getRoute(host: string): RouteConfig | undefined;
|
|
120
|
+
/**
|
|
121
|
+
* Get target for a host
|
|
122
|
+
*/
|
|
123
|
+
getTarget(host: string): {
|
|
124
|
+
target: undefined;
|
|
125
|
+
ssl: undefined;
|
|
126
|
+
remap: undefined;
|
|
127
|
+
} | {
|
|
128
|
+
target: string;
|
|
129
|
+
ssl: {
|
|
130
|
+
key: Buffer;
|
|
131
|
+
cert: Buffer;
|
|
132
|
+
ca?: Buffer;
|
|
133
|
+
allowHTTP1?: boolean;
|
|
134
|
+
} | undefined;
|
|
135
|
+
remap: ((url: string) => string) | undefined;
|
|
136
|
+
};
|
|
137
|
+
/**
|
|
138
|
+
* Get CORS headers for a request
|
|
139
|
+
*/
|
|
140
|
+
getCorsHeaders(origin: string, host?: string): OutgoingHttpHeaders;
|
|
141
|
+
/**
|
|
142
|
+
* Validate a request
|
|
143
|
+
*/
|
|
144
|
+
validate(info: ConnectionInfo): Promise<ForwardValidationResult>;
|
|
145
|
+
/**
|
|
146
|
+
* Get ports to listen on
|
|
147
|
+
*/
|
|
148
|
+
getPorts(): number[];
|
|
149
|
+
/**
|
|
150
|
+
* Get the full configuration
|
|
151
|
+
*/
|
|
152
|
+
getConfig(): ServerConfig;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Load configuration with cascading priority:
|
|
156
|
+
* 1. Programmatic config (highest priority)
|
|
157
|
+
* 2. Environment variables
|
|
158
|
+
* 3. Config file
|
|
159
|
+
* 4. Defaults (lowest priority)
|
|
160
|
+
*/
|
|
161
|
+
declare function loadConfig(programmaticConfig?: Partial<ServerConfig>): ProxyConfig;
|
|
162
|
+
/**
|
|
163
|
+
* Load configuration from a file (deprecated - use loadConfig instead)
|
|
164
|
+
*/
|
|
165
|
+
declare function loadConfigFromFile(path: string): ProxyConfig;
|
|
166
|
+
/**
|
|
167
|
+
* Load configuration from environment variables (deprecated - use loadConfig instead)
|
|
168
|
+
*/
|
|
169
|
+
declare function loadConfigFromEnv(): ProxyConfig;
|
|
170
|
+
|
|
171
|
+
export { ProxyConfig as P, RequestType as e, loadConfigFromFile as f, loadConfigFromEnv as g, loadConfig as l };
|
|
172
|
+
export type { CorsConfig as C, ForwardValidationResult as F, Router as R, ServerConfig as S, TLSWebSocket as T, WebSocketRouter as W, Streamer as a, RouteConfig as b, CertificateConfig as c, ConnectionInfo as d };
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { OutgoingHttpHeaders, IncomingMessage } from 'node:http';
|
|
2
|
+
import { IncomingHttpHeaders } from 'node:http2';
|
|
3
|
+
import { TLSSocket } from 'node:tls';
|
|
4
|
+
import WebSocket from 'ws';
|
|
5
|
+
|
|
6
|
+
declare enum RequestType {
|
|
7
|
+
API = "api",
|
|
8
|
+
STREAM = "stream",
|
|
9
|
+
WEBSOCKET = "websocket"
|
|
10
|
+
}
|
|
11
|
+
interface TLSWebSocket extends WebSocket {
|
|
12
|
+
_socket: TLSSocket;
|
|
13
|
+
}
|
|
14
|
+
type Router = (req: any, res: any) => void;
|
|
15
|
+
type WebSocketRouter = (req: IncomingMessage, socket: TLSWebSocket, headers: IncomingHttpHeaders) => void;
|
|
16
|
+
type Streamer = (stream: any, headers: any, flags: any) => void;
|
|
17
|
+
interface ForwardValidationResult {
|
|
18
|
+
status: boolean;
|
|
19
|
+
message?: string;
|
|
20
|
+
code?: number;
|
|
21
|
+
headers?: OutgoingHttpHeaders;
|
|
22
|
+
}
|
|
23
|
+
interface ConnectionInfo {
|
|
24
|
+
id: number;
|
|
25
|
+
method: string;
|
|
26
|
+
path: string;
|
|
27
|
+
remoteAddress: string;
|
|
28
|
+
scheme: string;
|
|
29
|
+
authority: string;
|
|
30
|
+
origin: string;
|
|
31
|
+
headers: IncomingHttpHeaders;
|
|
32
|
+
query: URLSearchParams;
|
|
33
|
+
type: RequestType;
|
|
34
|
+
respond: (status: number, headers?: Record<string, string>, body?: string) => void;
|
|
35
|
+
end: (body?: string) => void;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Route configuration for a specific domain/host
|
|
40
|
+
*/
|
|
41
|
+
interface RouteConfig {
|
|
42
|
+
/** Target host:port to proxy to */
|
|
43
|
+
target: string;
|
|
44
|
+
/** Enable SSL/TLS for the target */
|
|
45
|
+
ssl?: boolean;
|
|
46
|
+
/** Remap the URL path before forwarding */
|
|
47
|
+
remap?: (url: string) => string;
|
|
48
|
+
/** Custom CORS configuration */
|
|
49
|
+
cors?: CorsConfig;
|
|
50
|
+
/** Validation function for this route */
|
|
51
|
+
validate?: (info: ConnectionInfo) => Promise<ForwardValidationResult>;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* CORS configuration
|
|
55
|
+
*/
|
|
56
|
+
interface CorsConfig {
|
|
57
|
+
/** Allowed origins (array of strings or '*' for all) */
|
|
58
|
+
origin?: string | string[];
|
|
59
|
+
/** Allowed HTTP methods */
|
|
60
|
+
methods?: string[];
|
|
61
|
+
/** Allowed headers */
|
|
62
|
+
allowedHeaders?: string[];
|
|
63
|
+
/** Exposed headers */
|
|
64
|
+
exposedHeaders?: string[];
|
|
65
|
+
/** Allow credentials */
|
|
66
|
+
credentials?: boolean;
|
|
67
|
+
/** Max age for preflight cache */
|
|
68
|
+
maxAge?: number;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* SSL/TLS certificate configuration
|
|
72
|
+
*/
|
|
73
|
+
interface CertificateConfig {
|
|
74
|
+
/** Path to private key file or key content */
|
|
75
|
+
key: string;
|
|
76
|
+
/** Path to certificate file or cert content */
|
|
77
|
+
cert: string;
|
|
78
|
+
/** Path to CA file or CA content */
|
|
79
|
+
ca?: string;
|
|
80
|
+
/** Allow HTTP/1.1 fallback */
|
|
81
|
+
allowHTTP1?: boolean;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Server configuration
|
|
85
|
+
*/
|
|
86
|
+
interface ServerConfig {
|
|
87
|
+
/** SSL/TLS certificate configuration */
|
|
88
|
+
ssl?: CertificateConfig;
|
|
89
|
+
/** Route configurations mapped by host */
|
|
90
|
+
routes: Record<string, RouteConfig>;
|
|
91
|
+
/** Default CORS configuration */
|
|
92
|
+
cors?: CorsConfig;
|
|
93
|
+
/** Global validation function */
|
|
94
|
+
validate?: (info: ConnectionInfo) => Promise<ForwardValidationResult>;
|
|
95
|
+
/** Ports to listen on (defaults to [443, 8080, 9229]) */
|
|
96
|
+
ports?: number[];
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Proxy configuration manager
|
|
100
|
+
*/
|
|
101
|
+
declare class ProxyConfig {
|
|
102
|
+
private config;
|
|
103
|
+
private sslConfig?;
|
|
104
|
+
constructor(config: ServerConfig);
|
|
105
|
+
/**
|
|
106
|
+
* Initialize SSL configuration
|
|
107
|
+
*/
|
|
108
|
+
private initializeSSL;
|
|
109
|
+
/**
|
|
110
|
+
* Get SSL configuration
|
|
111
|
+
*/
|
|
112
|
+
getSSL(): {
|
|
113
|
+
key: Buffer;
|
|
114
|
+
cert: Buffer;
|
|
115
|
+
ca?: Buffer;
|
|
116
|
+
allowHTTP1?: boolean;
|
|
117
|
+
} | undefined;
|
|
118
|
+
/**
|
|
119
|
+
* Get route configuration for a host
|
|
120
|
+
*/
|
|
121
|
+
getRoute(host: string): RouteConfig | undefined;
|
|
122
|
+
/**
|
|
123
|
+
* Get target for a host
|
|
124
|
+
*/
|
|
125
|
+
getTarget(host: string): {
|
|
126
|
+
target: undefined;
|
|
127
|
+
ssl: undefined;
|
|
128
|
+
remap: undefined;
|
|
129
|
+
} | {
|
|
130
|
+
target: string;
|
|
131
|
+
ssl: {
|
|
132
|
+
key: Buffer;
|
|
133
|
+
cert: Buffer;
|
|
134
|
+
ca?: Buffer;
|
|
135
|
+
allowHTTP1?: boolean;
|
|
136
|
+
} | undefined;
|
|
137
|
+
remap: ((url: string) => string) | undefined;
|
|
138
|
+
};
|
|
139
|
+
/**
|
|
140
|
+
* Get CORS headers for a request
|
|
141
|
+
*/
|
|
142
|
+
getCorsHeaders(origin: string, host?: string): OutgoingHttpHeaders;
|
|
143
|
+
/**
|
|
144
|
+
* Validate a request
|
|
145
|
+
*/
|
|
146
|
+
validate(info: ConnectionInfo): Promise<ForwardValidationResult>;
|
|
147
|
+
/**
|
|
148
|
+
* Get ports to listen on
|
|
149
|
+
*/
|
|
150
|
+
getPorts(): number[];
|
|
151
|
+
/**
|
|
152
|
+
* Get the full configuration
|
|
153
|
+
*/
|
|
154
|
+
getConfig(): ServerConfig;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Load configuration with cascading priority:
|
|
158
|
+
* 1. Programmatic config (highest priority)
|
|
159
|
+
* 2. Environment variables
|
|
160
|
+
* 3. Config file
|
|
161
|
+
* 4. Defaults (lowest priority)
|
|
162
|
+
*/
|
|
163
|
+
declare function loadConfig(programmaticConfig?: Partial<ServerConfig>): ProxyConfig;
|
|
164
|
+
/**
|
|
165
|
+
* Load configuration from a file (deprecated - use loadConfig instead)
|
|
166
|
+
*/
|
|
167
|
+
declare function loadConfigFromFile(path: string): ProxyConfig;
|
|
168
|
+
/**
|
|
169
|
+
* Load configuration from environment variables (deprecated - use loadConfig instead)
|
|
170
|
+
*/
|
|
171
|
+
declare function loadConfigFromEnv(): ProxyConfig;
|
|
172
|
+
|
|
173
|
+
export { ProxyConfig as P, RequestType as e, loadConfigFromFile as f, loadConfigFromEnv as g, loadConfig as l };
|
|
174
|
+
export type { CorsConfig as C, ForwardValidationResult as F, Router as R, ServerConfig as S, TLSWebSocket as T, WebSocketRouter as W, Streamer as a, RouteConfig as b, CertificateConfig as c, ConnectionInfo as d };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { c as CertificateConfig, d as ConnectionInfo, C as CorsConfig, F as ForwardValidationResult, e as RequestType, b as RouteConfig,
|
|
1
|
+
import { P as ProxyConfig, T as TLSWebSocket, S as ServerConfig, R as Router, a as Streamer, W as WebSocketRouter } from './config-CeJ1tf8T.js';
|
|
2
|
+
export { c as CertificateConfig, d as ConnectionInfo, C as CorsConfig, F as ForwardValidationResult, e as RequestType, b as RouteConfig, l as loadConfig } from './config-CeJ1tf8T.js';
|
|
3
3
|
import { IncomingMessage, ServerResponse, IncomingHttpHeaders as IncomingHttpHeaders$1 } from 'node:http';
|
|
4
4
|
import { ServerHttp2Stream, IncomingHttpHeaders } from 'node:http2';
|
|
5
5
|
import 'node:tls';
|
|
6
6
|
import 'ws';
|
|
7
7
|
|
|
8
|
-
declare const restAPIProxyHandler: (req: IncomingMessage, res: ServerResponse) => Promise<void>;
|
|
8
|
+
declare const restAPIProxyHandler: (req: IncomingMessage, res: ServerResponse, config: ProxyConfig) => Promise<void>;
|
|
9
9
|
|
|
10
|
-
declare const streamAPIProxyHandler: (stream: ServerHttp2Stream, headers: IncomingHttpHeaders) => Promise<void>;
|
|
10
|
+
declare const streamAPIProxyHandler: (stream: ServerHttp2Stream, headers: IncomingHttpHeaders, config: ProxyConfig) => Promise<void>;
|
|
11
11
|
|
|
12
|
-
declare const websocketAPIProxyHandler: (req: IncomingMessage, socket: TLSWebSocket, headers: IncomingHttpHeaders$1) => Promise<void>;
|
|
12
|
+
declare const websocketAPIProxyHandler: (req: IncomingMessage, socket: TLSWebSocket, headers: IncomingHttpHeaders$1, config: ProxyConfig) => Promise<void>;
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* Proxy server instance
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import { createServer as createServer$1, request } from 'node:https';
|
|
|
5
5
|
import { constants, createSecureServer, createServer, connect } from 'node:http2';
|
|
6
6
|
import { exec } from 'node:child_process';
|
|
7
7
|
import { WebSocketServer, WebSocket } from 'ws';
|
|
8
|
-
import {
|
|
8
|
+
import { loadConfig } from './utils/config.js';
|
|
9
9
|
import 'node:fs';
|
|
10
10
|
import 'node:path';
|
|
11
11
|
|
|
@@ -212,11 +212,6 @@ function isMediaFile(path) {
|
|
|
212
212
|
".flac",
|
|
213
213
|
".aac",
|
|
214
214
|
".m4a",
|
|
215
|
-
".ogg",
|
|
216
|
-
".wav",
|
|
217
|
-
".flac",
|
|
218
|
-
".aac",
|
|
219
|
-
".m4a",
|
|
220
215
|
".woff2",
|
|
221
216
|
".woff",
|
|
222
217
|
".ttf",
|
|
@@ -229,8 +224,8 @@ function isMediaFile(path) {
|
|
|
229
224
|
});
|
|
230
225
|
}
|
|
231
226
|
|
|
232
|
-
const
|
|
233
|
-
|
|
227
|
+
const DEBUG$2 = process.env.DEBUG === "true";
|
|
228
|
+
const restAPIProxyHandler = async (req, res, config) => {
|
|
234
229
|
const { target, ssl, remap } = config.getTarget(req.headers.host || req.headers[":authority"]?.toString() || "");
|
|
235
230
|
if (req.httpVersion === "2.0" && ssl) return;
|
|
236
231
|
if (!target) {
|
|
@@ -238,12 +233,12 @@ const restAPIProxyHandler = async (req, res) => {
|
|
|
238
233
|
res.end("Not Found");
|
|
239
234
|
return;
|
|
240
235
|
}
|
|
241
|
-
console.log("HTTP1 rest proxy", `${ssl ? "https" : "http"}://${target}${req.url}`, req.headers.host);
|
|
236
|
+
if (DEBUG$2) console.log("HTTP1 rest proxy", `${ssl ? "https" : "http"}://${target}${req.url}`, req.headers.host);
|
|
242
237
|
if (remap) req.url = remap(req.url || "");
|
|
243
238
|
const requestFn = ssl ? request : request$1;
|
|
244
239
|
const headers = req.httpVersion === "2.0" ? http2HeadersToHttp1Headers(req.headers) : req.headers;
|
|
245
240
|
const method = req.httpVersion === "2.0" ? req.headers[":method"]?.toString() : req.method;
|
|
246
|
-
console.log("Proxy Request::", req.url, method, headers);
|
|
241
|
+
if (DEBUG$2) console.log("Proxy Request::", req.url, method, headers);
|
|
247
242
|
const proxy = requestFn(
|
|
248
243
|
`${ssl ? "https" : "http"}://${target}${req.url || ""}`,
|
|
249
244
|
{
|
|
@@ -268,11 +263,18 @@ const restAPIProxyHandler = async (req, res) => {
|
|
|
268
263
|
}
|
|
269
264
|
});
|
|
270
265
|
proxyRes.on("error", (error) => {
|
|
271
|
-
console.error("Proxy response error:", error);
|
|
266
|
+
if (DEBUG$2) console.error("Proxy response error:", error);
|
|
272
267
|
if (!res.destroyed) res.destroy(error);
|
|
273
268
|
});
|
|
274
269
|
}
|
|
275
270
|
);
|
|
271
|
+
proxy.on("error", (error) => {
|
|
272
|
+
if (DEBUG$2) console.error("Proxy request error:", error);
|
|
273
|
+
if (!res.headersSent) {
|
|
274
|
+
res.writeHead(502, { "Content-Type": "text/plain" });
|
|
275
|
+
res.end("Bad Gateway");
|
|
276
|
+
}
|
|
277
|
+
});
|
|
276
278
|
req.on("data", (chunk) => {
|
|
277
279
|
if (!proxy.writableEnded && !proxy.closed && !proxy.destroyed) {
|
|
278
280
|
proxy.write(chunk);
|
|
@@ -284,22 +286,27 @@ const restAPIProxyHandler = async (req, res) => {
|
|
|
284
286
|
}
|
|
285
287
|
});
|
|
286
288
|
req.on("error", (error) => {
|
|
287
|
-
console.error("Client request error:", error);
|
|
289
|
+
if (DEBUG$2) console.error("Client request error:", error);
|
|
288
290
|
if (!proxy.destroyed) proxy.destroy(error);
|
|
289
291
|
});
|
|
290
292
|
};
|
|
291
293
|
|
|
292
|
-
const
|
|
293
|
-
|
|
294
|
+
const DEBUG$1 = process.env.DEBUG === "true";
|
|
295
|
+
const streamAPIProxyHandler = async (stream, headers, config) => {
|
|
294
296
|
const { target, ssl, remap } = config.getTarget(headers[":authority"] || "");
|
|
295
297
|
if (!ssl) return;
|
|
296
298
|
if (!target) {
|
|
297
299
|
stream.destroy(new Error("Not Found"));
|
|
298
300
|
return;
|
|
299
301
|
}
|
|
300
|
-
|
|
302
|
+
if (DEBUG$1)
|
|
303
|
+
console.log(
|
|
304
|
+
"HTTP2 stream proxy",
|
|
305
|
+
`${ssl ? "https" : "http"}://${target}${headers[":path"]}`,
|
|
306
|
+
headers[":authority"]
|
|
307
|
+
);
|
|
301
308
|
if (remap) headers[":path"] = remap(headers[":path"] || "");
|
|
302
|
-
console.log("Proxy Request::", headers[":path"]);
|
|
309
|
+
if (DEBUG$1) console.log("Proxy Request::", headers[":path"]);
|
|
303
310
|
const proxy = connect(`https://${target}${headers[":path"]}`, {
|
|
304
311
|
...ssl,
|
|
305
312
|
rejectUnauthorized: false
|
|
@@ -308,7 +315,7 @@ const streamAPIProxyHandler = async (stream, headers) => {
|
|
|
308
315
|
const request = proxy.request(headers);
|
|
309
316
|
request.on("response", (headerResponse) => {
|
|
310
317
|
if (!stream.writableEnded && !stream.closed && !stream.destroyed) {
|
|
311
|
-
console.log("Proxy Response::", headerResponse[":status"], `for ${headers[":path"]}`);
|
|
318
|
+
if (DEBUG$1) console.log("Proxy Response::", headerResponse[":status"], `for ${headers[":path"]}`);
|
|
312
319
|
if (headers[":path"] && isMediaFile(headers[":path"])) {
|
|
313
320
|
headerResponse["cache-control"] = `public, max-age=${day()}`;
|
|
314
321
|
}
|
|
@@ -335,7 +342,7 @@ const streamAPIProxyHandler = async (stream, headers) => {
|
|
|
335
342
|
if (!stream.closed && !stream.destroyed) stream.close();
|
|
336
343
|
});
|
|
337
344
|
stream.on("error", (error) => {
|
|
338
|
-
console.error("HTTP2 stream proxy error:", error);
|
|
345
|
+
if (DEBUG$1) console.error("HTTP2 stream proxy error:", error);
|
|
339
346
|
if (!request.destroyed) request.destroy(error);
|
|
340
347
|
if (!proxy.closed) proxy.close();
|
|
341
348
|
});
|
|
@@ -353,28 +360,28 @@ const streamAPIProxyHandler = async (stream, headers) => {
|
|
|
353
360
|
if (!stream.closed && !stream.destroyed) stream.close();
|
|
354
361
|
});
|
|
355
362
|
request.on("error", (error) => {
|
|
356
|
-
console.error("HTTP2 request proxy error:", error);
|
|
363
|
+
if (DEBUG$1) console.error("HTTP2 request proxy error:", error);
|
|
357
364
|
if (!stream.destroyed) stream.destroy(error);
|
|
358
365
|
return !proxy.closed && proxy.close();
|
|
359
366
|
});
|
|
360
367
|
proxy.on("timeout", () => {
|
|
361
|
-
console.error("HTTP/2 client timeout");
|
|
368
|
+
if (DEBUG$1) console.error("HTTP/2 client timeout");
|
|
362
369
|
if (!stream.destroyed) stream.destroy(new Error("HTTP/2 client timeout"));
|
|
363
370
|
});
|
|
364
371
|
});
|
|
365
372
|
proxy.on("error", (error) => {
|
|
366
|
-
console.error("HTTP2 proxy connection error:", error);
|
|
373
|
+
if (DEBUG$1) console.error("HTTP2 proxy connection error:", error);
|
|
367
374
|
if (!stream.destroyed) {
|
|
368
375
|
stream.destroy(error);
|
|
369
376
|
}
|
|
370
377
|
});
|
|
371
378
|
};
|
|
372
379
|
|
|
373
|
-
const
|
|
374
|
-
|
|
380
|
+
const DEBUG = process.env.DEBUG === "true";
|
|
381
|
+
const websocketAPIProxyHandler = async (req, socket, headers, config) => {
|
|
375
382
|
const { target, ssl, remap } = config.getTarget(req.headers.host || "");
|
|
376
383
|
if (!target) return socket.close();
|
|
377
|
-
console.log("HTTP2 websocket proxy", `${ssl ? "https" : "http"}://${target}${req.url}`, headers.host);
|
|
384
|
+
if (DEBUG) console.log("HTTP2 websocket proxy", `${ssl ? "https" : "http"}://${target}${req.url}`, headers.host);
|
|
378
385
|
if (remap) req.url = remap(req.url || "");
|
|
379
386
|
const proxy = new WebSocket(
|
|
380
387
|
`${ssl ? "wss" : "ws"}://${target}${req.url || ""}`,
|
|
@@ -398,7 +405,7 @@ const websocketAPIProxyHandler = async (req, socket, headers) => {
|
|
|
398
405
|
proxy.on("close", () => socket.close());
|
|
399
406
|
socket.on("close", () => proxy.close());
|
|
400
407
|
proxy.on("error", (error) => {
|
|
401
|
-
console.error("WebSocket proxy error:", error);
|
|
408
|
+
if (DEBUG) console.error("WebSocket proxy error:", error);
|
|
402
409
|
socket.close();
|
|
403
410
|
});
|
|
404
411
|
};
|
|
@@ -422,14 +429,24 @@ class ProxyServer {
|
|
|
422
429
|
return { ssl, ports };
|
|
423
430
|
};
|
|
424
431
|
const createServers = ({ ssl: ssl2, ports: ports2 }) => {
|
|
432
|
+
const boundRestHandler = (req, res) => {
|
|
433
|
+
if (req.url === "/robiki-proxy/health" && req.method === "GET") {
|
|
434
|
+
res.writeHead(200, { "Content-Type": "text/plain" });
|
|
435
|
+
res.end("OK");
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
return restAPIProxyHandler(req, res, this.config);
|
|
439
|
+
};
|
|
440
|
+
const boundStreamHandler = (stream, headers) => streamAPIProxyHandler(stream, headers, this.config);
|
|
441
|
+
const boundWebsocketHandler = (req, socket, headers) => websocketAPIProxyHandler(req, socket, headers, this.config);
|
|
425
442
|
for (const port of ports2) {
|
|
426
443
|
let server;
|
|
427
444
|
if (ssl2) {
|
|
428
|
-
server = http2(
|
|
445
|
+
server = http2(boundRestHandler, boundStreamHandler, { ...ssl2, port });
|
|
429
446
|
} else {
|
|
430
|
-
server = http(
|
|
447
|
+
server = http(boundRestHandler, { port });
|
|
431
448
|
}
|
|
432
|
-
websocket(server,
|
|
449
|
+
websocket(server, boundWebsocketHandler, (info) => this.config.validate(info));
|
|
433
450
|
this.servers.push(server);
|
|
434
451
|
}
|
|
435
452
|
return this.servers;
|
|
@@ -448,9 +465,16 @@ class ProxyServer {
|
|
|
448
465
|
*/
|
|
449
466
|
async stop() {
|
|
450
467
|
console.log("Stopping proxy server...");
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
468
|
+
await Promise.all(
|
|
469
|
+
this.servers.map(
|
|
470
|
+
(server) => new Promise((resolve, reject) => {
|
|
471
|
+
server.close((err) => {
|
|
472
|
+
if (err) reject(err);
|
|
473
|
+
else resolve();
|
|
474
|
+
});
|
|
475
|
+
})
|
|
476
|
+
)
|
|
477
|
+
);
|
|
454
478
|
this.servers = [];
|
|
455
479
|
console.log("Proxy server stopped");
|
|
456
480
|
}
|
|
@@ -485,17 +509,27 @@ function createCustomProxy(config, handlers) {
|
|
|
485
509
|
return cfg;
|
|
486
510
|
};
|
|
487
511
|
const createServers = ({ ssl, ports, proxyConfig }) => {
|
|
512
|
+
const boundRestHandler = (req, res) => {
|
|
513
|
+
if (req.url === "/robiki-proxy/health" && req.method === "GET") {
|
|
514
|
+
res.writeHead(200, { "Content-Type": "text/plain" });
|
|
515
|
+
res.end("OK");
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
return restAPIProxyHandler(req, res, proxyConfig);
|
|
519
|
+
};
|
|
520
|
+
const boundStreamHandler = (stream, headers) => streamAPIProxyHandler(stream, headers, proxyConfig);
|
|
521
|
+
const boundWebsocketHandler = (req, socket, headers) => websocketAPIProxyHandler(req, socket, headers, proxyConfig);
|
|
488
522
|
for (const port of ports) {
|
|
489
523
|
let server;
|
|
490
524
|
if (ssl) {
|
|
491
|
-
server = http2(handlers.rest ||
|
|
525
|
+
server = http2(handlers.rest || boundRestHandler, handlers.stream || boundStreamHandler, {
|
|
492
526
|
...ssl,
|
|
493
527
|
port
|
|
494
528
|
});
|
|
495
529
|
} else {
|
|
496
|
-
server = http(handlers.rest ||
|
|
530
|
+
server = http(handlers.rest || boundRestHandler, { port });
|
|
497
531
|
}
|
|
498
|
-
websocket(server, handlers.websocket ||
|
|
532
|
+
websocket(server, handlers.websocket || boundWebsocketHandler, (info) => proxyConfig.validate(info));
|
|
499
533
|
servers.push(server);
|
|
500
534
|
}
|
|
501
535
|
return proxyConfig;
|
|
@@ -507,9 +541,16 @@ function createCustomProxy(config, handlers) {
|
|
|
507
541
|
start: async () => {
|
|
508
542
|
},
|
|
509
543
|
stop: async () => {
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
544
|
+
await Promise.all(
|
|
545
|
+
servers.map(
|
|
546
|
+
(server) => new Promise((resolve, reject) => {
|
|
547
|
+
server.close((err) => {
|
|
548
|
+
if (err) reject(err);
|
|
549
|
+
else resolve();
|
|
550
|
+
});
|
|
551
|
+
})
|
|
552
|
+
)
|
|
553
|
+
);
|
|
513
554
|
}
|
|
514
555
|
};
|
|
515
556
|
};
|
|
@@ -539,4 +580,4 @@ if (import.meta.url === `file://${process.argv[1]}`) {
|
|
|
539
580
|
Promise.resolve().then(() => startProxyServer()).catch((error) => handleStartupError(error));
|
|
540
581
|
}
|
|
541
582
|
|
|
542
|
-
export { ProxyServer, RequestType, createCustomProxy, createProxy,
|
|
583
|
+
export { ProxyServer, RequestType, createCustomProxy, createProxy, loadConfig, restAPIProxyHandler, streamAPIProxyHandler, websocketAPIProxyHandler };
|
package/dist/utils/config.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import 'node:http';
|
|
2
|
-
export { c as CertificateConfig, C as CorsConfig, P as ProxyConfig, b as RouteConfig, S as ServerConfig,
|
|
2
|
+
export { c as CertificateConfig, C as CorsConfig, P as ProxyConfig, b as RouteConfig, S as ServerConfig, l as loadConfig, g as loadConfigFromEnv, f as loadConfigFromFile } from '../config-CeJ1tf8T.js';
|
|
3
3
|
import 'node:http2';
|
|
4
4
|
import 'node:tls';
|
|
5
5
|
import 'ws';
|
package/dist/utils/config.js
CHANGED
|
@@ -131,7 +131,7 @@ class ProxyConfig {
|
|
|
131
131
|
* Get ports to listen on
|
|
132
132
|
*/
|
|
133
133
|
getPorts() {
|
|
134
|
-
return [443, 8080, 9229];
|
|
134
|
+
return this.config.ports || [443, 8080, 9229];
|
|
135
135
|
}
|
|
136
136
|
/**
|
|
137
137
|
* Get the full configuration
|
|
@@ -140,17 +140,6 @@ class ProxyConfig {
|
|
|
140
140
|
return this.config;
|
|
141
141
|
}
|
|
142
142
|
}
|
|
143
|
-
let globalConfig;
|
|
144
|
-
function initConfig(config) {
|
|
145
|
-
globalConfig = new ProxyConfig(config);
|
|
146
|
-
return globalConfig;
|
|
147
|
-
}
|
|
148
|
-
function getConfig() {
|
|
149
|
-
if (!globalConfig) {
|
|
150
|
-
throw new Error("Configuration not initialized. Call initConfig() first.");
|
|
151
|
-
}
|
|
152
|
-
return globalConfig;
|
|
153
|
-
}
|
|
154
143
|
function deepMerge(...objects) {
|
|
155
144
|
const result = {};
|
|
156
145
|
for (const obj of objects) {
|
|
@@ -206,13 +195,13 @@ function loadConfig(programmaticConfig) {
|
|
|
206
195
|
const fileConfig = getConfigFromFile();
|
|
207
196
|
const envConfig = getConfigFromEnv();
|
|
208
197
|
const merged = deepMerge(defaults, fileConfig, envConfig, programmaticConfig || {});
|
|
209
|
-
return
|
|
198
|
+
return new ProxyConfig(merged);
|
|
210
199
|
}
|
|
211
200
|
function loadConfigFromFile(path) {
|
|
212
201
|
try {
|
|
213
202
|
const configFile = readFileSync(resolve(path), "utf-8");
|
|
214
203
|
const config = JSON.parse(configFile);
|
|
215
|
-
return
|
|
204
|
+
return new ProxyConfig(config);
|
|
216
205
|
} catch (error) {
|
|
217
206
|
console.error("Failed to load configuration file:", error);
|
|
218
207
|
throw error;
|
|
@@ -220,7 +209,7 @@ function loadConfigFromFile(path) {
|
|
|
220
209
|
}
|
|
221
210
|
function loadConfigFromEnv() {
|
|
222
211
|
const config = getConfigFromEnv();
|
|
223
|
-
return
|
|
212
|
+
return new ProxyConfig(config);
|
|
224
213
|
}
|
|
225
214
|
|
|
226
|
-
export { ProxyConfig,
|
|
215
|
+
export { ProxyConfig, loadConfig, loadConfigFromEnv, loadConfigFromFile };
|