@nmtjs/proxy 0.15.0-beta.3 → 0.15.0-beta.31
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/Cargo.lock +2 -0
- package/Cargo.toml +5 -3
- package/package.json +8 -3
- package/src/errors.rs +28 -0
- package/src/lb.rs +45 -0
- package/src/lib.rs +5 -3
- package/src/options.rs +178 -0
- package/src/proxy.rs +642 -102
- package/src/router.rs +452 -345
- package/src/server.rs +80 -0
- package/src/config.rs +0 -199
package/src/server.rs
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
use std::{collections::HashMap, sync::Arc};
|
|
2
|
+
|
|
3
|
+
use tokio::{sync::Mutex, task::JoinHandle};
|
|
4
|
+
use tokio_util::sync::CancellationToken;
|
|
5
|
+
|
|
6
|
+
#[allow(dead_code)]
|
|
7
|
+
#[derive(Clone)]
|
|
8
|
+
pub struct Server {
|
|
9
|
+
services: Arc<Mutex<HashMap<String, ServiceHandle>>>,
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
#[allow(dead_code)]
|
|
13
|
+
pub struct ServiceHandle {
|
|
14
|
+
pub cancel: CancellationToken,
|
|
15
|
+
pub task: JoinHandle<()>,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
#[allow(dead_code)]
|
|
19
|
+
#[derive(Debug)]
|
|
20
|
+
pub enum AddServiceError {
|
|
21
|
+
AlreadyExists,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
impl Server {
|
|
25
|
+
pub fn new() -> Self {
|
|
26
|
+
Self {
|
|
27
|
+
services: Arc::new(Mutex::new(HashMap::new())),
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
#[allow(dead_code)]
|
|
32
|
+
pub async fn add_service(
|
|
33
|
+
&self,
|
|
34
|
+
name: String,
|
|
35
|
+
service: ServiceHandle,
|
|
36
|
+
) -> Result<(), AddServiceError> {
|
|
37
|
+
let mut guard = self.services.lock().await;
|
|
38
|
+
if guard.contains_key(&name) {
|
|
39
|
+
return Err(AddServiceError::AlreadyExists);
|
|
40
|
+
}
|
|
41
|
+
guard.insert(name, service);
|
|
42
|
+
Ok(())
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
pub async fn upsert_service(&self, name: String, service: ServiceHandle) {
|
|
46
|
+
let old = {
|
|
47
|
+
let mut guard = self.services.lock().await;
|
|
48
|
+
guard.insert(name, service)
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
if let Some(old) = old {
|
|
52
|
+
old.cancel.cancel();
|
|
53
|
+
let _ = old.task.await;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
pub async fn remove_service(&self, name: &str) {
|
|
58
|
+
let old = {
|
|
59
|
+
let mut guard = self.services.lock().await;
|
|
60
|
+
guard.remove(name)
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
if let Some(old) = old {
|
|
64
|
+
old.cancel.cancel();
|
|
65
|
+
let _ = old.task.await;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
pub async fn stop_all(&self) {
|
|
70
|
+
let services = {
|
|
71
|
+
let mut guard = self.services.lock().await;
|
|
72
|
+
std::mem::take(&mut *guard)
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
for (_, handle) in services {
|
|
76
|
+
handle.cancel.cancel();
|
|
77
|
+
let _ = handle.task.await;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
package/src/config.rs
DELETED
|
@@ -1,199 +0,0 @@
|
|
|
1
|
-
use napi::bindgen_prelude::*;
|
|
2
|
-
use napi_derive::napi;
|
|
3
|
-
use std::{collections::HashMap, time::Duration};
|
|
4
|
-
use url::Url;
|
|
5
|
-
|
|
6
|
-
#[napi(object)]
|
|
7
|
-
pub struct ApplicationUpstreamConfig {
|
|
8
|
-
pub url: String,
|
|
9
|
-
#[napi(ts_type = "'http' | 'websocket'")]
|
|
10
|
-
pub r#type: String,
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
#[napi(object)]
|
|
14
|
-
pub struct ApplicationConfig {
|
|
15
|
-
pub upstreams: Vec<ApplicationUpstreamConfig>,
|
|
16
|
-
pub sni: Option<String>,
|
|
17
|
-
pub enable_tls: Option<bool>,
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
#[napi(object)]
|
|
21
|
-
pub struct ProxyOptions {
|
|
22
|
-
pub listen: Option<String>,
|
|
23
|
-
pub tls: Option<bool>,
|
|
24
|
-
pub threads: Option<u16>,
|
|
25
|
-
pub health_check_interval: Option<u32>,
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
|
|
29
|
-
pub enum UpstreamKind {
|
|
30
|
-
Http,
|
|
31
|
-
Websocket,
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
impl UpstreamKind {
|
|
35
|
-
pub fn as_str(&self) -> &'static str {
|
|
36
|
-
match self {
|
|
37
|
-
Self::Http => "http",
|
|
38
|
-
Self::Websocket => "websocket",
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
#[derive(Debug, Clone)]
|
|
44
|
-
pub enum AppUpstream {
|
|
45
|
-
Port {
|
|
46
|
-
secure: bool,
|
|
47
|
-
#[allow(dead_code)]
|
|
48
|
-
address: String,
|
|
49
|
-
port: u16,
|
|
50
|
-
hostname: String,
|
|
51
|
-
},
|
|
52
|
-
Unix {
|
|
53
|
-
secure: bool,
|
|
54
|
-
path: String,
|
|
55
|
-
},
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
pub struct AppDefinition {
|
|
59
|
-
pub upstreams: HashMap<UpstreamKind, Vec<AppUpstream>>,
|
|
60
|
-
pub sni: Option<String>,
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
pub struct ProxyConfig {
|
|
64
|
-
pub apps: HashMap<String, AppDefinition>,
|
|
65
|
-
pub listen: Option<String>,
|
|
66
|
-
pub threads: Option<u16>,
|
|
67
|
-
pub tls: bool,
|
|
68
|
-
pub health_check_interval: Option<Duration>,
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
impl ProxyConfig {
|
|
72
|
-
pub fn from_inputs(
|
|
73
|
-
apps: HashMap<String, ApplicationConfig>,
|
|
74
|
-
options: Option<ProxyOptions>,
|
|
75
|
-
) -> Result<Self> {
|
|
76
|
-
if apps.is_empty() {
|
|
77
|
-
return Err(Error::from_reason(
|
|
78
|
-
"apps map must contain at least one application",
|
|
79
|
-
));
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
let mut normalized_apps = HashMap::with_capacity(apps.len());
|
|
83
|
-
for (name, config) in apps {
|
|
84
|
-
if config.upstreams.is_empty() {
|
|
85
|
-
return Err(Error::from_reason(format!(
|
|
86
|
-
"application '{name}' must provide at least one upstream"
|
|
87
|
-
)));
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
let mut upstreams = HashMap::new();
|
|
91
|
-
|
|
92
|
-
for upstream in config.upstreams {
|
|
93
|
-
let trimmed = upstream.url.trim().to_string();
|
|
94
|
-
if trimmed.is_empty() {
|
|
95
|
-
return Err(Error::from_reason(format!(
|
|
96
|
-
"upstream URL cannot be empty in application '{name}'"
|
|
97
|
-
)));
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
let parsed = Url::parse(&trimmed).map_err(|_| {
|
|
101
|
-
Error::from_reason(format!(
|
|
102
|
-
"invalid URL '{trimmed}' in upstreams for application '{name}'"
|
|
103
|
-
))
|
|
104
|
-
})?;
|
|
105
|
-
|
|
106
|
-
let secure = match parsed.scheme() {
|
|
107
|
-
"https" | "wss" | "https+unix" | "wss+unix" => true,
|
|
108
|
-
"http" | "ws" | "http+unix" | "ws+unix" => false,
|
|
109
|
-
other => {
|
|
110
|
-
return Err(Error::from_reason(format!(
|
|
111
|
-
"unsupported URL scheme '{other}' in upstream '{trimmed}'"
|
|
112
|
-
)));
|
|
113
|
-
}
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
let upstream_type = match upstream.r#type.as_str() {
|
|
117
|
-
"http" => UpstreamKind::Http,
|
|
118
|
-
"websocket" => UpstreamKind::Websocket,
|
|
119
|
-
other => {
|
|
120
|
-
return Err(Error::from_reason(format!(
|
|
121
|
-
"unsupported upstream type '{other}' for application '{name}'"
|
|
122
|
-
)));
|
|
123
|
-
}
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
let upstream = if parsed.scheme().ends_with("+unix") {
|
|
127
|
-
AppUpstream::Unix {
|
|
128
|
-
secure,
|
|
129
|
-
path: parsed.path().to_string(),
|
|
130
|
-
}
|
|
131
|
-
} else {
|
|
132
|
-
let host = parsed.host_str().ok_or_else(|| {
|
|
133
|
-
Error::from_reason(format!(
|
|
134
|
-
"missing host in upstream URL '{trimmed}' for application '{name}'"
|
|
135
|
-
))
|
|
136
|
-
})?;
|
|
137
|
-
let port = parsed.port_or_known_default().ok_or_else(|| {
|
|
138
|
-
Error::from_reason(format!(
|
|
139
|
-
"missing port in upstream URL '{trimmed}' for application '{name}'"
|
|
140
|
-
))
|
|
141
|
-
})?;
|
|
142
|
-
AppUpstream::Port {
|
|
143
|
-
secure,
|
|
144
|
-
address: parsed.to_string(),
|
|
145
|
-
port,
|
|
146
|
-
hostname: host.to_string(),
|
|
147
|
-
}
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
upstreams
|
|
151
|
-
.entry(upstream_type)
|
|
152
|
-
.or_insert_with(Vec::new)
|
|
153
|
-
.push(upstream);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
if upstreams.is_empty() {
|
|
157
|
-
return Err(Error::from_reason(format!(
|
|
158
|
-
"application '{name}' produced no valid upstreams"
|
|
159
|
-
)));
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
let definition = AppDefinition {
|
|
163
|
-
upstreams,
|
|
164
|
-
sni: config.sni.filter(|value| !value.is_empty()),
|
|
165
|
-
};
|
|
166
|
-
|
|
167
|
-
normalized_apps.insert(name, definition);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
let options = options.unwrap_or_default();
|
|
171
|
-
|
|
172
|
-
let health_check_interval = options
|
|
173
|
-
.health_check_interval
|
|
174
|
-
.map(|secs| Duration::from_secs(secs as u64));
|
|
175
|
-
|
|
176
|
-
Ok(Self {
|
|
177
|
-
apps: normalized_apps,
|
|
178
|
-
listen: options.listen,
|
|
179
|
-
threads: options.threads,
|
|
180
|
-
tls: options.tls.unwrap_or(false),
|
|
181
|
-
health_check_interval,
|
|
182
|
-
})
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
pub fn listener(&self) -> &str {
|
|
186
|
-
self.listen.as_deref().unwrap_or("0.0.0.0:6188")
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
impl Default for ProxyOptions {
|
|
191
|
-
fn default() -> Self {
|
|
192
|
-
Self {
|
|
193
|
-
listen: None,
|
|
194
|
-
threads: None,
|
|
195
|
-
tls: None,
|
|
196
|
-
health_check_interval: Some(30),
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
}
|