Compare commits

..

No commits in common. "b2c8afcd69f5a8aad66424f9c1c92fb7d5265af6" and "e846e65cab97d53ee96715ffbfc10f1435bf0fc3" have entirely different histories.

6 changed files with 403 additions and 679 deletions

921
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,14 +1,15 @@
[package] [package]
name = "speedtest-rs" name = "speedtest-rs"
version = "1.0.0" version = "0.1.0"
edition = "2021" edition = "2021"
license = "LGPL-3.0+" license = "LGPL-3.0+"
[dependencies] [dependencies]
rocket = { version = "0.5.0-rc.2", features = ["json"] } rocket = { version = "0.5.0-rc.1", features = ["json"] }
rocket-client-addr = "0.5.2" rand = { version = "0.8.4" }
rand = { version = "0.8.5" } rocket-client-addr = "0.5.0"
regex = "1" regex = "1.5.4"
ipinfo = "0.3.1" ipinfo = "0.2.0"
serde_with = { version = "2.0.1", features = ["json"] } serde_with = { version = "1.10.0", features = ["json"] }
clap = "4.0.11" rocket_cors = { git = "https://github.com/lawliet89/rocket_cors", rev = "a062933" }
dotenv = "0.15.0"

View file

@ -2,10 +2,6 @@
This is a lightweight backend written in Rust for [Librespeed](https://github.com/librespeed/speedtest). This is a lightweight backend written in Rust for [Librespeed](https://github.com/librespeed/speedtest).
Fork from: https://github.com/camilohe/speedtest-rs.git
Original repos: https://github.com/drobson03/speedtest-rs.git
## Compatibility ## Compatibility
Supported by all Librespeed frontends, though some features are missing (see below). Supported by all Librespeed frontends, though some features are missing (see below).
@ -34,7 +30,7 @@ You need Rust 1.55+ to compile the binary.
1. Clone this repository: 1. Clone this repository:
```bash ```bash
git clone https://cloud.silique.fr/gitea/Silique/speedtest-rs.git git clone github.com/drobson03/speedtest-rs
# Change current working directory to the repository # Change current working directory to the repository
cd speedtest-rs cd speedtest-rs
``` ```
@ -45,15 +41,29 @@ cd speedtest-rs
cargo build --release cargo build --release
``` ```
3. Copy the `assets` directory and the compiled `speedtest-rs` binary into a single directory. 3. Copy the `assets` directory and the compiled `speedtest-rs` binary into a single directory along with a copy of `.env.example` named `.env` with your preferred port, listen address and [IPinfo.io](https://ipinfo.io/) API token.
4. Put `assets` folder under the same directory as your compiled binary.
5. Put `assets` folder under the same directory as your compiled binary.
- Make sure font files and JavaScript files are in the `assets` directory - Make sure font files and JavaScript files are in the `assets` directory
- You can have multiple HTML pages under `assets` directory. They can be access directly under the server root - You can have multiple HTML pages under `assets` directory. They can be access directly under the server root
(e.g. `/example-singleServer-full.html`) (e.g. `/example-singleServer-full.html`)
- It's possible to have a default page mapped to `/`, simply put a file named `index.html` under `assets` - It's possible to have a default page mapped to `/`, simply put a file named `index.html` under `assets`
5. Launch: ./speedtest-rs 6. Change `.env` according to your environment:
```sh
# your ipinfo.io API token
IPINFO_TOKEN=
# your server's latitude
SERVER_LATITUDE=1
# your server's longitude
SERVER_LONGITUDE=1
# the port to bind to
ROCKET_PORT=8000
# the bind address (0.0.0.0 is all interfaces)
ROCKET_ADDRESS=0.0.0.0
```
## Differences between Go and PHP implementation ## Differences between Go and PHP implementation
@ -64,8 +74,6 @@ Copyright (C) 2016-2021 Federico Dossena
Copyright (C) 2021 Darcy Robson Copyright (C) 2021 Darcy Robson
Copyright (C) 2022 Emmanuel Garette
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, either version 3 of the License, or

View file

@ -2,11 +2,11 @@ use ipinfo::{IpDetails, IpInfo, IpInfoConfig};
use regex::Regex; use regex::Regex;
use rocket::serde::{json::Json, Serialize}; use rocket::serde::{json::Json, Serialize};
use rocket_client_addr::ClientRealAddr; use rocket_client_addr::ClientRealAddr;
use rocket::State; use std::env::var;
use crate::haversine::Units; use crate::haversine::Units;
use crate::serialized_ip_info::IpDetailsDef; use crate::serialized_ip_info::IpDetailsDef;
use crate::util::{get_client_server_distance_string, get_ip_type, Config}; use crate::util::{get_client_server_distance_string, get_ip_type};
#[derive(FromForm)] #[derive(FromForm)]
pub struct GetIPOptions { pub struct GetIPOptions {
@ -27,7 +27,7 @@ pub struct GetIPResponse {
} }
#[get("/getIP?<opts..>")] #[get("/getIP?<opts..>")]
pub async fn get_ip(client_addr: &ClientRealAddr, opts: GetIPOptions, state: &State<Config>) -> Json<GetIPResponse> { pub async fn get_ip(client_addr: &ClientRealAddr, opts: GetIPOptions) -> Json<GetIPResponse> {
let mut result = GetIPResponse { let mut result = GetIPResponse {
processed_string: None, processed_string: None,
raw_isp_info: None, raw_isp_info: None,
@ -42,8 +42,7 @@ pub async fn get_ip(client_addr: &ClientRealAddr, opts: GetIPOptions, state: &St
} }
if opts.isp { if opts.isp {
let config = state.inner(); let ipinfo = get_ipinfo(&ip).await;
let ipinfo = get_ipinfo(&ip, &config).await;
result.raw_isp_info = Some(ipinfo.to_owned()); result.raw_isp_info = Some(ipinfo.to_owned());
let org_regex = Regex::new(r"AS\d+\s").unwrap(); let org_regex = Regex::new(r"AS\d+\s").unwrap();
@ -63,7 +62,7 @@ pub async fn get_ip(client_addr: &ClientRealAddr, opts: GetIPOptions, state: &St
} }
if !ipinfo.loc.is_empty() { if !ipinfo.loc.is_empty() {
let distance = get_client_server_distance_string(ipinfo.loc, opts.distance, config); let distance = get_client_server_distance_string(ipinfo.loc, opts.distance);
isp = format!("{} ({})", &isp, &distance); isp = format!("{} ({})", &isp, &distance);
} }
@ -74,26 +73,25 @@ pub async fn get_ip(client_addr: &ClientRealAddr, opts: GetIPOptions, state: &St
} }
#[get("/getIP.php?<opts..>")] #[get("/getIP.php?<opts..>")]
pub async fn get_ip_php(client_addr: &ClientRealAddr, opts: GetIPOptions, state: &State<Config>) -> Json<GetIPResponse> { pub async fn get_ip_php(client_addr: &ClientRealAddr, opts: GetIPOptions) -> Json<GetIPResponse> {
get_ip(client_addr, opts, state).await get_ip(client_addr, opts).await
} }
#[get("/backend/getIP.php?<opts..>")] #[get("/backend/getIP.php?<opts..>")]
pub async fn get_backend_ip_php( pub async fn get_backend_ip_php(
client_addr: &ClientRealAddr, client_addr: &ClientRealAddr,
opts: GetIPOptions, opts: GetIPOptions,
state: &State<Config>,
) -> Json<GetIPResponse> { ) -> Json<GetIPResponse> {
get_ip(client_addr, opts, state).await get_ip(client_addr, opts).await
} }
pub async fn get_ipinfo(ip: &str, config: &Config) -> IpDetails { pub async fn get_ipinfo(ip: &str) -> IpDetails {
let ipconfig = IpInfoConfig { let config = IpInfoConfig {
token: Some(config.ipinfo_token.to_string()), token: Some(var("IPINFO_TOKEN").unwrap_or(String::new())),
..Default::default() ..Default::default()
}; };
let mut ipinfo_client = IpInfo::new(ipconfig).expect("should construct"); let mut ipinfo_client = IpInfo::new(config).expect("should construct");
let res = ipinfo_client.lookup(&[ip]).unwrap(); let res = ipinfo_client.lookup(&[ip]).unwrap();
let ipinfo = res.get(ip).unwrap(); let ipinfo = res.get(ip).unwrap();

View file

@ -1,4 +1,3 @@
#![allow(unused_must_use)]
#[macro_use] #[macro_use]
extern crate rocket; extern crate rocket;
@ -12,31 +11,22 @@ pub mod haversine;
pub mod serialized_ip_info; pub mod serialized_ip_info;
pub mod util; pub mod util;
use dotenv::dotenv;
use std::error::Error; use std::error::Error;
use clap::{arg, Command, value_parser};
use rocket::fs::FileServer; use rocket::fs::FileServer;
use rocket::http::Method;
use rocket_cors::{AllowedHeaders, AllowedOrigins, Cors};
use empty::*; use empty::*;
use garbage::*; use garbage::*;
use get_ip::*; use get_ip::*;
use util::Config;
#[rocket::main] #[rocket::main]
async fn main() -> Result<(), Box<dyn Error>> { async fn main() -> Result<(), Box<dyn Error>> {
dotenv().ok();
let args = Command::new("speedtest-rs") let cors = setup_cors().await?;
.version("1.0.0")
.author("Emmanuel Garette <egarette@silique.fr>")
.about("Alternative implemention of the Librespeed server API")
.arg(arg!(-i --ip <VALUE>).default_value("127.0.0.1"))
.arg(arg!(-p --port <VALUE>).value_parser(value_parser!(u16)).default_value("8000"))
.arg(arg!(-a --assets <VALUE>).default_value("assets"))
.arg(arg!(-t --ipinfo_token <VALUE>).default_value(""))
.arg(arg!(-l --latitude <VALUE>).value_parser(value_parser!(f64)).default_value("0.0"))
.arg(arg!(-o --longitude <VALUE>).value_parser(value_parser!(f64)).default_value("0.0"))
.get_matches();
let routes = routes![get_ip::get_ip, get_backend_ip_php]; let routes = routes![get_ip::get_ip, get_backend_ip_php];
let garbage_routes = routes![ let garbage_routes = routes![
@ -56,23 +46,36 @@ async fn main() -> Result<(), Box<dyn Error>> {
]; ];
let routes = vec![routes, garbage_routes, empty_routes].concat(); let routes = vec![routes, garbage_routes, empty_routes].concat();
let config = Config {
ip: args.get_one::<String>("ip").expect("required").to_string(),
port: *args.get_one::<u16>("port").expect("required"),
assets: args.get_one::<String>("assets").expect("required").to_string(),
ipinfo_token: args.get_one::<String>("ipinfo_token").expect("required").to_string(),
latitude: *args.get_one::<f64>("latitude").expect("required"),
longitude: *args.get_one::<f64>("longitude").expect("required"),
};
let figment = rocket::Config::figment()
.merge(("address", &config.ip))
.merge(("port", &config.port));
let asset_path = std::env::current_dir().unwrap().join(args.get_one::<String>("assets").expect("required")); let mut rocketship = rocket::build().attach(cors).mount("/", routes);
rocket::custom(figment).mount("/", routes)
.manage(config) let asset_path = std::env::current_dir().unwrap().join("assets");
.mount("/", FileServer::from(asset_path)) if asset_path.exists() {
.launch().await?; let fileserver = FileServer::from(asset_path);
rocketship = rocketship.mount("/", fileserver);
}
rocketship.launch().await?;
Ok(()) Ok(())
} }
async fn setup_cors() -> Result<Cors, Box<dyn Error>> {
let allowed_origins = AllowedOrigins::all();
let allowed_methods = vec![Method::Get, Method::Post]
.into_iter()
.map(From::from)
.collect();
let allowed_headers = AllowedHeaders::some(&["Content-Encoding", "Content-Type"]);
let cors = rocket_cors::CorsOptions {
allowed_origins,
allowed_methods,
allowed_headers,
..Default::default()
}
.to_cors()?;
Ok(cors)
}

View file

@ -1,3 +1,5 @@
use std::env::var;
use crate::haversine::{distance, Location, Units}; use crate::haversine::{distance, Location, Units};
use regex::Regex; use regex::Regex;
@ -41,27 +43,24 @@ pub fn parse_location_string(location: String) -> Result<Location, String> {
}) })
} }
pub fn get_client_server_distance_string(client_location: String, units: Units, config: &Config) -> String { pub fn get_client_server_distance_string(client_location: String, units: Units) -> String {
let client_location = parse_location_string(client_location).unwrap_or(Location { let client_location = parse_location_string(client_location).unwrap_or(Location {
latitude: 0.0, latitude: 0.0,
longitude: 0.0, longitude: 0.0,
}); });
let server_location = Location { let server_location = Location {
latitude: config.latitude, latitude: var("SERVER_LATITUDE")
longitude: config.longitude, .unwrap_or_default()
.parse::<f64>()
.unwrap_or_default(),
longitude: var("SERVER_LONGITUDE")
.unwrap_or_default()
.parse::<f64>()
.unwrap_or_default(),
}; };
let distance = distance(client_location, server_location, &units); let distance = distance(client_location, server_location, &units);
format!("{:.2} {}", distance, units) format!("{:.2} {}", distance, units)
} }
pub struct Config {
pub ip: String,
pub port: u16,
pub assets: String,
pub ipinfo_token: String,
pub latitude: f64,
pub longitude: f64,
}