mirror of
https://github.com/telemt/telemt.git
synced 2026-11-01 13:21:58 +03:00
Configurable HTTP Methods for Bridge Carriers
Co-Authored-By: brekotis <93345790+brekotis@users.noreply.github.com>
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
//! | `network` | `dns_overrides` | Applied immediately |
|
||||
//! | `access` | All user/quota fields | Effective immediately |
|
||||
//! | `web` | Carrier, timing, and debug policy | Applied to newly issued sessions |
|
||||
//! | `web` | `carrier_method` | Applied to newly rendered bridge pages |
|
||||
//! Fields that require re-binding sockets (`server.listeners`, legacy
|
||||
//! `server.port`, `censorship.*`, `network.*`, `use_middle_proxy`) are **not**
|
||||
//! applied; a warning is emitted. SYN limiter rules are process-owned and are
|
||||
@@ -67,3 +68,7 @@ use watcher::{ReloadState, reload_config};
|
||||
mod base_path_tests;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
// Carrier method reloads preserve page-owned requests and process-owned limits.
|
||||
#[cfg(test)]
|
||||
#[path = "hot_reload/carrier_method_tests.rs"]
|
||||
mod carrier_method_tests;
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
use super::*;
|
||||
|
||||
use crate::config::WebCarrierMethod;
|
||||
|
||||
#[test]
|
||||
fn carrier_method_reload_is_hot_and_preserves_process_limits() {
|
||||
let mut active = ProxyConfig::default();
|
||||
for method in [WebCarrierMethod::Put, WebCarrierMethod::Post] {
|
||||
let mut desired = active.clone();
|
||||
desired.web.carrier_method = method;
|
||||
assert_eq!(classify_config_changes(&active, &desired).changed, ["web"]);
|
||||
assert!(!classify_config_changes(&active, &desired).restart_required);
|
||||
desired.web.limits.max_http_connections += 1;
|
||||
let applied = overlay_hot_fields(&active, &desired);
|
||||
assert_eq!(applied.web.carrier_method, method);
|
||||
assert_eq!(
|
||||
applied.web.limits.max_http_connections,
|
||||
active.web.limits.max_http_connections
|
||||
);
|
||||
active = applied;
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn carrier_method_reload_publishes_both_directions_and_keeps_last_good_value() {
|
||||
let directory = tempfile::tempdir().unwrap();
|
||||
let path = directory.path().join("config.toml");
|
||||
let source = |method| format!("[web]\ncarrier_method = \"{method}\"\n");
|
||||
std::fs::write(&path, source("post")).unwrap();
|
||||
let initial = Arc::new(ProxyConfig::load(&path).unwrap());
|
||||
let initial_hash = ProxyConfig::load_with_metadata(&path)
|
||||
.unwrap()
|
||||
.rendered_hash;
|
||||
let (config_tx, _config_rx) = watch::channel(Arc::clone(&initial));
|
||||
let (log_tx, _log_rx) = watch::channel(initial.general.log_level.clone());
|
||||
let mut reload_state = ReloadState::new(Some(initial_hash));
|
||||
for (token, method) in [
|
||||
("put", WebCarrierMethod::Put),
|
||||
("post", WebCarrierMethod::Post),
|
||||
] {
|
||||
std::fs::write(&path, source(token)).unwrap();
|
||||
reload_config(&path, &config_tx, &log_tx, None, None, &mut reload_state).unwrap();
|
||||
let applied = config_tx.borrow().clone();
|
||||
assert_eq!(applied.web.carrier_method, method);
|
||||
std::fs::write(&path, source("PATCH")).unwrap();
|
||||
reload_config(&path, &config_tx, &log_tx, None, None, &mut reload_state);
|
||||
let unchanged = config_tx.borrow().clone();
|
||||
assert!(Arc::ptr_eq(&unchanged, &applied));
|
||||
}
|
||||
}
|
||||
@@ -263,6 +263,7 @@ const LISTENER_CONFIG_KEYS: &[&str] = &[
|
||||
const WEB_CONFIG_KEYS: &[&str] = &[
|
||||
"enabled",
|
||||
"carrier",
|
||||
"carrier_method",
|
||||
"carriers",
|
||||
"carrier_learning",
|
||||
"carrier_negotiation_aggressiveness",
|
||||
|
||||
@@ -3,6 +3,10 @@ use super::*;
|
||||
#[path = "web_tests/base_path_tests.rs"]
|
||||
mod base_path_tests;
|
||||
|
||||
// Method selection must not alter capability identity or accepted config literals.
|
||||
#[path = "web_tests/carrier_method_tests.rs"]
|
||||
mod carrier_method_tests;
|
||||
|
||||
const WEB_CONFIG: &str = r#"
|
||||
[access.users]
|
||||
alice = "000102030405060708090a0b0c0d0e0f"
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn carrier_method_defaults_and_roundtrips_without_changing_capabilities() {
|
||||
let default = load_config_from_temp_toml(WEB_CONFIG);
|
||||
assert_eq!(default.web.carrier_method, WebCarrierMethod::Post);
|
||||
let capabilities = default.web.runtime.as_ref().unwrap().capabilities.clone();
|
||||
for (token, method) in [
|
||||
("post", WebCarrierMethod::Post),
|
||||
("put", WebCarrierMethod::Put),
|
||||
] {
|
||||
let configured = WEB_CONFIG.replace(
|
||||
"carrier = \"https-lanes\"",
|
||||
&format!("carrier = \"https-lanes\"\ncarrier_method = \"{token}\""),
|
||||
);
|
||||
let source = format!("[general]\nconfig_strict = true\n{configured}");
|
||||
let config = load_config_from_temp_toml(&source);
|
||||
assert_eq!(config.web.carrier_method, method);
|
||||
assert_eq!(
|
||||
config.web.runtime.as_ref().unwrap().capabilities,
|
||||
capabilities
|
||||
);
|
||||
let json = serde_json::to_value(&config.web).unwrap();
|
||||
assert_eq!(json["carrier_method"], token);
|
||||
let decoded: WebConfig = serde_json::from_value(json).unwrap();
|
||||
assert_eq!(decoded.carrier_method, method);
|
||||
let serialized = toml::to_string(&config.web).unwrap();
|
||||
let decoded: WebConfig = toml::from_str(&serialized).unwrap();
|
||||
assert_eq!(decoded.carrier_method, method);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn carrier_method_rejects_unknown_tokens_types_and_aliases() {
|
||||
for value in ["\"POST\"", "\"PUT\"", "\"patch\"", "true", "42", "[]"] {
|
||||
let source = WEB_CONFIG.replace(
|
||||
"carrier = \"https-lanes\"",
|
||||
&format!("carrier = \"https-lanes\"\ncarrier_method = {value}"),
|
||||
);
|
||||
assert!(load_config_error_from_temp_toml(&source).contains("carrier_method"));
|
||||
}
|
||||
let configured = WEB_CONFIG.replace(
|
||||
"carrier = \"https-lanes\"",
|
||||
"carrier = \"https-lanes\"\nhttp_method = \"put\"",
|
||||
);
|
||||
let source = format!("[general]\nconfig_strict = true\n{configured}");
|
||||
assert!(load_config_error_from_temp_toml(&source).contains("http_method"));
|
||||
}
|
||||
+1
-1
@@ -63,7 +63,7 @@ pub(crate) use web::{
|
||||
};
|
||||
pub(crate) use web_carrier::WEB_CARRIER_LEARNING_MIN_ENTRIES;
|
||||
#[allow(unused_imports)]
|
||||
pub use web_carrier::{WebCarrier, WebCarriers};
|
||||
pub use web_carrier::{WebCarrier, WebCarrierMethod, WebCarriers};
|
||||
pub(crate) use web_debug::web_debug_fits_limits;
|
||||
pub use web_debug::{WebDebugBodyCapture, WebDebugConfig};
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ use std::sync::Arc;
|
||||
use bytes::Bytes;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::web_carrier::{WebCarrier, WebCarriers};
|
||||
use super::web_carrier::{WebCarrier, WebCarrierMethod, WebCarriers};
|
||||
use super::web_debug::WebDebugConfig;
|
||||
|
||||
// Serialized WEB defaults remain separate from the runtime data model.
|
||||
@@ -413,6 +413,9 @@ pub struct WebConfig {
|
||||
/// Sole carrier when negotiation is disabled and final fallback when enabled.
|
||||
#[serde(default)]
|
||||
pub carrier: WebCarrier,
|
||||
/// Page-owned HTTP method for HTTPS uplink and downlink, including retries.
|
||||
#[serde(default)]
|
||||
pub carrier_method: WebCarrierMethod,
|
||||
/// Ordered carriers considered by server-side negotiation before the fallback carrier.
|
||||
#[serde(default)]
|
||||
pub carriers: WebCarriers,
|
||||
@@ -471,6 +474,7 @@ impl Default for WebConfig {
|
||||
Self {
|
||||
enabled: false,
|
||||
carrier: WebCarrier::default(),
|
||||
carrier_method: WebCarrierMethod::default(),
|
||||
carriers: WebCarriers::default(),
|
||||
carrier_learning: default_web_carrier_learning(),
|
||||
carrier_negotiation_aggressiveness: WebCarrierNegotiationAggressiveness::default(),
|
||||
|
||||
@@ -3,6 +3,27 @@ use serde::{Deserialize, Serialize};
|
||||
/// Minimum restart-owned entries required for one complete learning sample.
|
||||
pub(crate) const WEB_CARRIER_LEARNING_MIN_ENTRIES: usize = 3;
|
||||
|
||||
/// HTTP method emitted by newly rendered HTTPS carrier bridges.
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum WebCarrierMethod {
|
||||
/// Preserve the original bridge request method.
|
||||
#[default]
|
||||
Post,
|
||||
/// Allow idempotent-method connection reuse in Linux WebKitGTK.
|
||||
Put,
|
||||
}
|
||||
|
||||
impl WebCarrierMethod {
|
||||
/// Returns the HTTP token embedded in the page-owned carrier policy.
|
||||
pub(crate) const fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::Post => "POST",
|
||||
Self::Put => "PUT",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Carrier selected for one newly issued WEB relay session.
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
|
||||
Reference in New Issue
Block a user