@nmtjs/proxy 0.15.0-beta.9 → 1.0.0-alpha.0

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/src/router.rs DELETED
@@ -1,406 +0,0 @@
1
- use crate::config::{AppUpstream, ProxyConfig, UpstreamKind};
2
- use async_trait::async_trait;
3
- use http::{Uri, uri::PathAndQuery};
4
- use log::{debug, info};
5
- use napi::Error as NapiError;
6
- use napi::bindgen_prelude::Result as NapiResult;
7
- use pingora::{
8
- http::ResponseHeader,
9
- lb::{Backend, Backends, discovery::Static},
10
- prelude::*,
11
- protocols::l4::socket::SocketAddr as PingoraSocketAddr,
12
- services::background::{GenBackgroundService, background_service},
13
- };
14
- use pingora_load_balancing::health_check::TcpHealthCheck;
15
- use std::{
16
- collections::{BTreeSet, HashMap},
17
- net::ToSocketAddrs,
18
- sync::Arc,
19
- time::Duration,
20
- };
21
-
22
- pub type Cluster = LoadBalancer<RoundRobin>;
23
-
24
- pub struct ClusterEntry {
25
- pub balancer: Arc<Cluster>,
26
- pub sni: Option<String>,
27
- }
28
-
29
- pub struct Router {
30
- clusters: HashMap<String, HashMap<UpstreamKind, ClusterEntry>>,
31
- upstreams_tls: HashMap<String, bool>,
32
- }
33
-
34
- #[derive(Default)]
35
- pub struct RouterCtx {
36
- app_info: AppInfoState,
37
- }
38
-
39
- struct AppInfo {
40
- name: String,
41
- kind: UpstreamKind,
42
- }
43
-
44
- #[derive(Default)]
45
- enum AppInfoState {
46
- #[default]
47
- Unknown,
48
- Missing,
49
- Found(AppInfo),
50
- }
51
-
52
- impl RouterCtx {
53
- fn app_info(&mut self, session: &Session) -> Option<&AppInfo> {
54
- if matches!(self.app_info, AppInfoState::Unknown) {
55
- self.app_info = match extract_app_name(session) {
56
- Some(name) => AppInfoState::Found(AppInfo {
57
- name: name.to_string(),
58
- kind: if session.is_upgrade_req() {
59
- UpstreamKind::Websocket
60
- } else {
61
- UpstreamKind::Http
62
- },
63
- }),
64
- None => AppInfoState::Missing,
65
- };
66
- }
67
-
68
- match &self.app_info {
69
- AppInfoState::Found(info) => Some(info),
70
- _ => None,
71
- }
72
- }
73
- }
74
-
75
- impl Router {
76
- fn new(
77
- clusters: HashMap<String, HashMap<UpstreamKind, ClusterEntry>>,
78
- upstreams_tls: HashMap<String, bool>,
79
- ) -> Self {
80
- Self {
81
- clusters,
82
- upstreams_tls,
83
- }
84
- }
85
-
86
- fn cluster_for<'a>(&'a self, app_info: &AppInfo) -> Option<&'a ClusterEntry> {
87
- self.clusters
88
- .get(&app_info.name)
89
- .and_then(|entries| entries.get(&app_info.kind))
90
- }
91
- }
92
-
93
- #[async_trait]
94
- impl ProxyHttp for Router {
95
- type CTX = RouterCtx;
96
-
97
- fn new_ctx(&self) -> Self::CTX {
98
- RouterCtx::default()
99
- }
100
-
101
- async fn upstream_peer(
102
- &self,
103
- session: &mut Session,
104
- ctx: &mut RouterCtx,
105
- ) -> Result<Box<HttpPeer>> {
106
- const NO_CLUSTER: ImmutStr = ImmutStr::Static("no matching application for request");
107
-
108
- let app_info = ctx.app_info(session).ok_or_else(|| {
109
- Error::create(
110
- ErrorType::ConnectError,
111
- ErrorSource::Internal,
112
- Some(NO_CLUSTER),
113
- None,
114
- )
115
- })?;
116
- let cluster = self.cluster_for(app_info).ok_or_else(|| {
117
- Error::create(
118
- ErrorType::ConnectError,
119
- ErrorSource::Internal,
120
- Some(NO_CLUSTER),
121
- None,
122
- )
123
- })?;
124
- const NO_UPSTREAM: ImmutStr = ImmutStr::Static("no available upstream for application");
125
- let upstream = cluster.balancer.select(b"", 256).ok_or_else(|| {
126
- Error::create(
127
- ErrorType::ConnectError,
128
- ErrorSource::Internal,
129
- Some(NO_UPSTREAM),
130
- None,
131
- )
132
- })?;
133
-
134
- let sni = cluster
135
- .sni
136
- .clone()
137
- .or_else(|| session.req_header().uri.host().map(|h| h.to_string()))
138
- .or_else(|| {
139
- session
140
- .req_header()
141
- .headers
142
- .get("host")
143
- .and_then(|v| v.to_str().ok())
144
- .map(|v| v.split(':').next().unwrap_or(v).to_string())
145
- })
146
- .unwrap_or_default();
147
-
148
- let enable_tls = *self
149
- .upstreams_tls
150
- .get(&upstream.addr.to_string())
151
- .unwrap_or(&false);
152
-
153
- match upstream.addr {
154
- PingoraSocketAddr::Inet(_) => Ok(Box::new(HttpPeer::new(upstream, enable_tls, sni))),
155
- PingoraSocketAddr::Unix(addr) => {
156
- let path = addr.as_pathname().and_then(|p| p.to_str()).ok_or_else(|| {
157
- Error::create(
158
- ErrorType::InternalError,
159
- ErrorSource::Internal,
160
- Some(ImmutStr::Static("invalid unix socket path")),
161
- None,
162
- )
163
- })?;
164
- let peer = HttpPeer::new_uds(path, enable_tls, sni).map_err(|e| {
165
- Error::create(
166
- ErrorType::InternalError,
167
- ErrorSource::Internal,
168
- Some(ImmutStr::Static("failed to create uds peer")),
169
- Some(Box::new(e)),
170
- )
171
- })?;
172
- Ok(Box::new(peer))
173
- }
174
- }
175
- }
176
-
177
- async fn upstream_request_filter(
178
- &self,
179
- session: &mut Session,
180
- upstream_request: &mut RequestHeader,
181
- ctx: &mut Self::CTX,
182
- ) -> Result<()> {
183
- let Some(app_info) = ctx.app_info(session) else {
184
- return Ok(());
185
- };
186
-
187
- if let Some(client_addr) = session.client_addr()
188
- && let Some(inet) = client_addr.as_inet()
189
- {
190
- let client_ip = inet.ip().to_string();
191
- let new_val = if let Some(existing) = upstream_request.headers.get("x-forwarded-for") {
192
- if let Ok(existing_str) = existing.to_str() {
193
- format!("{}, {}", existing_str, client_ip)
194
- } else {
195
- client_ip
196
- }
197
- } else {
198
- client_ip
199
- };
200
- upstream_request
201
- .insert_header("x-forwarded-for", new_val)
202
- .map_err(|e| {
203
- Error::create(
204
- ErrorType::InternalError,
205
- ErrorSource::Internal,
206
- Some(ImmutStr::Static("failed to set x-forwarded-for")),
207
- Some(Box::new(e)),
208
- )
209
- })?;
210
- }
211
-
212
- let name = app_info.name.as_str();
213
-
214
- let path = upstream_request.uri.path();
215
- let path_bytes = path.as_bytes();
216
- let mut start_idx = 0;
217
- while start_idx < path_bytes.len() && path_bytes[start_idx] == b'/' {
218
- start_idx += 1;
219
- }
220
-
221
- if path[start_idx..].starts_with(name) {
222
- let end_idx = start_idx + name.len();
223
- if end_idx == path.len() || path_bytes[end_idx] == b'/' {
224
- let mut new_path = &path[end_idx..];
225
- if new_path.is_empty() {
226
- new_path = "/";
227
- }
228
-
229
- let mut parts = upstream_request.uri.clone().into_parts();
230
- let path_and_query = if let Some(query) = upstream_request.uri.query() {
231
- let mut s = String::with_capacity(new_path.len() + 1 + query.len());
232
- s.push_str(new_path);
233
- s.push('?');
234
- s.push_str(query);
235
- s
236
- } else {
237
- new_path.to_string()
238
- };
239
-
240
- let pq = path_and_query.parse::<PathAndQuery>().map_err(|e| {
241
- Error::create(
242
- ErrorType::InternalError,
243
- ErrorSource::Internal,
244
- Some(ImmutStr::Static("invalid path")),
245
- Some(Box::new(e)),
246
- )
247
- })?;
248
-
249
- parts.path_and_query = Some(pq);
250
- let new_uri = Uri::from_parts(parts).map_err(|e| {
251
- Error::create(
252
- ErrorType::InternalError,
253
- ErrorSource::Internal,
254
- Some(ImmutStr::Static("invalid uri")),
255
- Some(Box::new(e)),
256
- )
257
- })?;
258
-
259
- debug!(
260
- "Rewriting upstream URI from {} to {}",
261
- upstream_request.uri, new_uri
262
- );
263
- upstream_request.set_uri(new_uri);
264
- }
265
- }
266
- Ok(())
267
- }
268
-
269
- async fn response_filter(
270
- &self,
271
- _session: &mut Session,
272
- _upstream_response: &mut ResponseHeader,
273
- _ctx: &mut Self::CTX,
274
- ) -> Result<()> {
275
- const REMOVE_HEADERS: [&str; 1] = ["uWebSockets"];
276
- for header in REMOVE_HEADERS {
277
- _upstream_response.remove_header(header);
278
- }
279
- Ok(())
280
- }
281
- }
282
-
283
- pub struct RouterAssembly {
284
- pub router: Router,
285
- pub background_services: Vec<GenBackgroundService<Cluster>>,
286
- }
287
-
288
- pub fn build_router(config: &ProxyConfig) -> NapiResult<RouterAssembly> {
289
- let mut services = Vec::with_capacity(config.apps.len());
290
- let mut clusters = HashMap::with_capacity(config.apps.len());
291
- let mut upstreams_tls = HashMap::new();
292
-
293
- for (name, definition) in &config.apps {
294
- let mut app_clusters = HashMap::new();
295
-
296
- for (&kind, upstreams) in &definition.upstreams {
297
- let mut resolved_addrs = Vec::new();
298
- for upstream in upstreams {
299
- match upstream {
300
- AppUpstream::Port {
301
- secure,
302
- hostname,
303
- port,
304
- ..
305
- } => {
306
- let addr_str = format!("{}:{}", hostname, port);
307
- let addrs = addr_str.to_socket_addrs().map_err(|e| {
308
- NapiError::from_reason(format!(
309
- "failed to resolve '{}': {}",
310
- addr_str, e
311
- ))
312
- })?;
313
- for addr in addrs {
314
- let p_addr = PingoraSocketAddr::Inet(addr);
315
- resolved_addrs.push(p_addr.clone());
316
- upstreams_tls.insert(p_addr.to_string(), *secure);
317
- }
318
- }
319
- AppUpstream::Unix { secure, path } => {
320
- let p_addr = PingoraSocketAddr::Unix(
321
- std::os::unix::net::SocketAddr::from_pathname(path).map_err(|e| {
322
- NapiError::from_reason(format!(
323
- "failed to resolve unix socket '{}': {}",
324
- path, e
325
- ))
326
- })?,
327
- );
328
- resolved_addrs.push(p_addr.clone());
329
- upstreams_tls.insert(p_addr.to_string(), *secure);
330
- }
331
- }
332
- }
333
-
334
- if resolved_addrs.is_empty() {
335
- continue;
336
- }
337
-
338
- let cluster_name = format!("{}:{}", name, kind.as_str());
339
- let (balancer, service) =
340
- build_cluster_service(&cluster_name, resolved_addrs, config.health_check_interval)?;
341
-
342
- app_clusters.insert(
343
- kind,
344
- ClusterEntry {
345
- balancer,
346
- sni: definition.sni.clone(),
347
- },
348
- );
349
- if let Some(service) = service {
350
- services.push(service);
351
- }
352
- }
353
-
354
- if !app_clusters.is_empty() {
355
- clusters.insert(name.clone(), app_clusters);
356
- }
357
- }
358
-
359
- let router = Router::new(clusters, upstreams_tls);
360
- Ok(RouterAssembly {
361
- router,
362
- background_services: services,
363
- })
364
- }
365
-
366
- fn build_cluster_service(
367
- name: &str,
368
- upstreams: Vec<PingoraSocketAddr>,
369
- health_interval: Option<Duration>,
370
- ) -> NapiResult<(Arc<Cluster>, Option<GenBackgroundService<Cluster>>)> {
371
- info!(
372
- "Building cluster for app '{name}' with upstreams: {:?}",
373
- upstreams
374
- );
375
- let backends_vec: Vec<Backend> = upstreams
376
- .into_iter()
377
- .map(|addr| {
378
- let addr_str = addr.to_string();
379
- Backend::new(&addr_str).map_err(|e| {
380
- NapiError::from_reason(format!(
381
- "failed to create backend for '{}': {}",
382
- addr_str, e
383
- ))
384
- })
385
- })
386
- .collect::<Result<Vec<_>, _>>()?;
387
- let backends_set: BTreeSet<Backend> = backends_vec.into_iter().collect();
388
- let discovery = Static::new(backends_set);
389
- let backends = Backends::new(discovery);
390
- let mut balancer = LoadBalancer::from_backends(backends);
391
-
392
- if let Some(interval) = health_interval {
393
- balancer.set_health_check(TcpHealthCheck::new());
394
- balancer.health_check_frequency = Some(interval);
395
- let service = background_service("cluster health check", balancer);
396
- Ok((service.task(), Some(service)))
397
- } else {
398
- Ok((Arc::new(balancer), None))
399
- }
400
- }
401
-
402
- fn extract_app_name(session: &Session) -> Option<&str> {
403
- let path = session.req_header().uri.path();
404
- let without_slash = path.trim_start_matches('/');
405
- without_slash.split('/').find(|segment| !segment.is_empty())
406
- }