mirror of
https://github.com/euzu/tuliprox.git
synced 2026-10-01 13:32:04 +02:00
added unit to throttle
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
# Changelog
|
||||
# 2.2.6 (2025-03-xx)
|
||||
- !BREAKING CHANGE! bandwidth `throttle_kbps` attribute for `reverse_proxy.stream` in `config.yml`
|
||||
is now `throttle` and supports units. Allowed units are `KB/s`,`MB/s`,`KiB/s`,`MiB/s`,`kbps`,`mbps`,`Mibps`.
|
||||
Default unit is `kbps`.
|
||||
- Added rate limiting per IP. The burst_size defines the initial number of available connections,
|
||||
while period_millis specifies the interval at which one connection is replenished.
|
||||
If behind a proxy `x-forwarded-for`, `x-real-ip` or `forwarded` should be set as header.
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
"ACTIVE_CLIENTS": "Active Clients",
|
||||
"RESOURCE_REWRITE_DISABLE": "Resource Rewrite disable",
|
||||
"RECONNECT": "Reconnect",
|
||||
"THROTTLE_KBPS": "Throttle kbps",
|
||||
"THROTTLE": "Throttle",
|
||||
"GRACE_PERIOD": "Grace period ms",
|
||||
"BUFFER_ENABLED": "Buffer Enabled",
|
||||
"BUFFER_SIZE": "Buffer Size",
|
||||
@@ -150,7 +150,8 @@
|
||||
"ACTIVE_CLIENTS": "Activates active clients info log"
|
||||
},
|
||||
"PROXY": {
|
||||
"RESOURCE_REWRITE_DISABLE": "If activated the URL rewrite of resources in reverse proxy mode is disabled"
|
||||
"RESOURCE_REWRITE_DISABLE": "If activated the URL rewrite of resources in reverse proxy mode is disabled",
|
||||
"THROTTLE": "Supported units are KB/s,MB/s,KiB/s,MiB/s,kbps,mbps,Mibps"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +68,11 @@ export default function FormView(props: FormViewProps) {
|
||||
for (const fieldDef of fields) {
|
||||
if (fieldDef.name === field) {
|
||||
if (fieldDef.fieldType === FormFieldType.NUMBER) {
|
||||
value = parseInt(value);
|
||||
if (value.trim().isEmpty()) {
|
||||
value = undefined;
|
||||
} else {
|
||||
value = parseInt(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,8 +60,8 @@ const CONFIG_REVERSE_PROXY_FIELDS = [
|
||||
|
||||
const CONFIG_REVERSE_PROXY_STREAM_FIELDS = [
|
||||
{name: 'retry', label: 'LABEL.RECONNECT', fieldType: FormFieldType.CHECK},
|
||||
{name: 'throttle_kbps', label: 'LABEL.THROTTLE_KBPS', fieldType: FormFieldType.TEXT},
|
||||
{name: 'grace_period_millis', label: 'LABEL.GRACE_PERIOD', fieldType: FormFieldType.TEXT},
|
||||
{name: 'throttle', label: 'LABEL.THROTTLE', hint: 'HINT.CONFIG.PROXY.THROTTLE', fieldType: FormFieldType.TEXT},
|
||||
{name: 'grace_period_millis', label: 'LABEL.GRACE_PERIOD', fieldType: FormFieldType.NUMBER},
|
||||
];
|
||||
|
||||
const CONFIG_REVERSE_PROXY_STREAM_BUFFER_FIELDS = [
|
||||
@@ -195,7 +195,7 @@ export default function MainConfigView(props: MainConfigViewProps) {
|
||||
|
||||
let cfgReverseProxyStreamBuffer: StreamConfig = {
|
||||
retry: reverseProxyStreamConfig.retry,
|
||||
throttle_kpbs: reverseProxyStreamConfig.throttle_kpbs,
|
||||
throttle: reverseProxyStreamConfig.throttle,
|
||||
grace_period_millis: reverseProxyStreamConfig.grace_period_millis,
|
||||
buffer: {
|
||||
enabled: reverseProxyStreamBufferConfig.enabled,
|
||||
|
||||
@@ -193,7 +193,7 @@ export interface StreamBufferConfig {
|
||||
export interface StreamConfig {
|
||||
retry: boolean,
|
||||
buffer?: StreamBufferConfig,
|
||||
throttle_kpbs?: number,
|
||||
throttle?: number,
|
||||
grace_period_millis?: number,
|
||||
}
|
||||
|
||||
|
||||
@@ -351,13 +351,8 @@ pub async fn stream_response(app_state: &AppState,
|
||||
|
||||
let throttle_kbps = get_stream_throttle(app_state);
|
||||
|
||||
let body_stream = if throttle_kbps > 0 {
|
||||
match item_type {
|
||||
PlaylistItemType::Video | PlaylistItemType::Series | PlaylistItemType::SeriesInfo => {
|
||||
axum::body::Body::from_stream(ThrottledStream::new(stream.boxed(), throttle_kbps as usize))
|
||||
}
|
||||
_ => axum::body::Body::from_stream(stream)
|
||||
}
|
||||
let body_stream = if throttle_kbps > 0 && matches!(item_type, PlaylistItemType::Video | PlaylistItemType::Series | PlaylistItemType::SeriesInfo) {
|
||||
axum::body::Body::from_stream(ThrottledStream::new(stream.boxed(), throttle_kbps as usize))
|
||||
} else {
|
||||
axum::body::Body::from_stream(stream)
|
||||
};
|
||||
@@ -372,12 +367,12 @@ pub async fn stream_response(app_state: &AppState,
|
||||
axum::http::StatusCode::BAD_REQUEST.into_response()
|
||||
}
|
||||
|
||||
fn get_stream_throttle(app_state: &AppState) -> u32 {
|
||||
fn get_stream_throttle(app_state: &AppState) -> u64 {
|
||||
app_state.config
|
||||
.reverse_proxy
|
||||
.as_ref()
|
||||
.and_then(|reverse_proxy| reverse_proxy.stream.as_ref())
|
||||
.map(|stream| stream.throttle_kbps).map_or(0, |t| t.unwrap_or_default())
|
||||
.map(|stream| stream.throttle_kbps).unwrap_or_default()
|
||||
}
|
||||
|
||||
async fn shared_stream_response(app_state: &AppState, stream_url: &str, user: &ProxyUserCredentials) -> Option<impl IntoResponse> {
|
||||
|
||||
@@ -201,7 +201,6 @@ impl ProxyUserCredentials {
|
||||
|
||||
pub async fn connection_permission(&self, app_state: &AppState) -> UserConnectionPermission {
|
||||
if app_state.config.user_access_control {
|
||||
|
||||
// we allow requests with max connection reached, but we should block streaming after grace period
|
||||
if let Some(&max_connections) = self.max_connections.as_ref() {
|
||||
if max_connections > 0 {
|
||||
|
||||
+10
-4
@@ -26,7 +26,7 @@ use crate::utils::default_utils::{default_as_default, default_as_true, default_a
|
||||
use crate::utils::file::file_lock_manager::FileLockManager;
|
||||
use crate::utils::file::file_utils;
|
||||
use crate::utils::file::file_utils::file_reader;
|
||||
use crate::utils::size_utils::parse_size_base_2;
|
||||
use crate::utils::size_utils::{parse_size_base_2, parse_to_kbps};
|
||||
use crate::utils::sys_utils::exit;
|
||||
|
||||
const DEFAULT_USER_AGENT: &str = "Mozilla/5.0 (AppleTV; U; CPU OS 14_2 like Mac OS X; en-us) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.1 Safari/605.1.15";
|
||||
@@ -1175,18 +1175,24 @@ pub struct StreamConfig {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub buffer: Option<StreamBufferConfig>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub throttle_kbps: Option<u32>,
|
||||
pub throttle: Option<String>,
|
||||
#[serde(default = "default_grace_period_millis")]
|
||||
pub grace_period_millis: u64,
|
||||
#[serde(default)]
|
||||
pub forced_retry_interval_secs: u32,
|
||||
#[serde(default, skip)]
|
||||
pub throttle_kbps: u64,
|
||||
}
|
||||
|
||||
impl StreamConfig {
|
||||
fn prepare(&mut self) {
|
||||
fn prepare(&mut self) -> Result<(), M3uFilterError> {
|
||||
if let Some(buffer) = self.buffer.as_mut() {
|
||||
buffer.prepare();
|
||||
}
|
||||
if let Some (throttle) = &self.throttle {
|
||||
self.throttle_kbps = parse_to_kbps(throttle).map_err(|err| M3uFilterError::new(M3uFilterErrorKind::Info, err))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1227,7 +1233,7 @@ pub struct ReverseProxyConfig {
|
||||
impl ReverseProxyConfig {
|
||||
fn prepare(&mut self, working_dir: &str) -> Result<(), M3uFilterError> {
|
||||
if let Some(stream) = self.stream.as_mut() {
|
||||
stream.prepare();
|
||||
stream.prepare()?;
|
||||
}
|
||||
if let Some(cache) = self.cache.as_mut() {
|
||||
if cache.enabled && self.resource_rewrite_disabled {
|
||||
|
||||
@@ -63,4 +63,48 @@ pub fn human_readable_byte_size(bytes: u64) -> String {
|
||||
}
|
||||
|
||||
format!("{size:.2} {unit}")
|
||||
}
|
||||
|
||||
pub fn parse_to_kbps(input: &str) -> Result<u64, String> {
|
||||
// Define unit conversion factors (in bits per second)
|
||||
let units: &[(&str, u64)] = &[
|
||||
("KB/s", 8), // Kilobytes per second to kbps
|
||||
("MB/s", 8000), // Megabytes per second to kbps
|
||||
("KiB/s", 8 * 1024 / 1000), // Kibibytes per second to kbps
|
||||
("MiB/s", 8 * 1024), // Mebibytes per second to kbps
|
||||
("kbps", 1), // Kilobits per second (already in kbps)
|
||||
("Kbps", 1), // Kilobits per second (already in kbps)
|
||||
("mbps", 1000), // Megabits per second to kbps
|
||||
("Mbps", 1000), // Megabits per second to kbps
|
||||
("Mibps", 1024), // Mebibits per second to kbps
|
||||
];
|
||||
|
||||
let speed_str = input.trim();
|
||||
for (unit, multiplier) in units {
|
||||
if speed_str.ends_with(unit) {
|
||||
let number_part = speed_str[..speed_str.len() - unit.len()].trim();
|
||||
let value = u64::from_str(number_part).map_err(|_| format!("Invalid speed: {number_part}"))?;
|
||||
return value.checked_mul(*multiplier).ok_or_else(|| format!("Speed too large: {speed_str}"));
|
||||
}
|
||||
}
|
||||
|
||||
u64::from_str(&speed_str).map_err(|_| format!("Invalid speed: {speed_str}, supported units are {}", units.iter().map(|p| p.0).collect::<Vec<_>>().join(",")))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::utils::size_utils::parse_to_kbps;
|
||||
|
||||
#[test]
|
||||
fn test_parse_kpbs() {
|
||||
assert_eq!(parse_to_kbps("1KB/s").unwrap(), 8);
|
||||
assert_eq!(parse_to_kbps("1MB/s").unwrap(), 8000);
|
||||
assert_eq!(parse_to_kbps("1KiB/s").unwrap(), 8 * 1024 / 1000);
|
||||
assert_eq!(parse_to_kbps("1MiB/s").unwrap(), 8 * 1024);
|
||||
assert_eq!(parse_to_kbps("1kbps").unwrap(), 1);
|
||||
assert_eq!(parse_to_kbps("1mbps").unwrap(), 1000);
|
||||
assert_eq!(parse_to_kbps("1Kbps").unwrap(), 1);
|
||||
assert_eq!(parse_to_kbps("1Mbps").unwrap(), 1000);
|
||||
assert_eq!(parse_to_kbps("1Mibps").unwrap(), 1024);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user