diff --git a/Cargo.lock b/Cargo.lock index 5580b7b..aa87e5f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -158,6 +158,12 @@ version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.5.0" @@ -244,6 +250,16 @@ dependencies = [ "version_check", ] +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.6" @@ -314,6 +330,12 @@ dependencies = [ "syn 2.0.48", ] +[[package]] +name = "data-encoding" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" + [[package]] name = "deranged" version = "0.3.11" @@ -390,6 +412,21 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -634,6 +671,23 @@ dependencies = [ "itoa", "pin-project-lite", "tokio", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", ] [[package]] @@ -651,6 +705,8 @@ dependencies = [ "pin-project-lite", "socket2", "tokio", + "tower", + "tower-service", "tracing", ] @@ -683,6 +739,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "indexmap" version = "2.1.0" @@ -702,6 +768,12 @@ dependencies = [ "generic-array", ] +[[package]] +name = "ipnet" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" + [[package]] name = "itoa" version = "1.0.10" @@ -825,6 +897,24 @@ dependencies = [ "version_check", ] +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "nix" version = "0.27.1" @@ -896,6 +986,50 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "openssl" +version = "0.10.62" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cde4d2d9200ad5909f8dac647e29482e07c3a35de8a13fce7c9c7747ad9f671" +dependencies = [ + "bitflags 2.4.1", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1665caf8ab2dc9aef43d1c0023bd904633a6a05cb30b0ad59bec2ae986e57a7" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "ordered-multimap" version = "0.4.3" @@ -992,6 +1126,26 @@ dependencies = [ "sha2", ] +[[package]] +name = "pin-project" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "pin-project-lite" version = "0.2.13" @@ -1004,6 +1158,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkg-config" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69d3587f8a9e599cc7ec2c00e331f71c4e69a5f9a4b8a6efd5b07466b9736f9a" + [[package]] name = "poem" version = "2.0.0" @@ -1011,6 +1171,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38a712ff53e257d60d3d22936c51cafa606552129d55539c8a400de44eff676d" dependencies = [ "async-trait", + "base64 0.21.7", "bytes", "chrono", "cookie", @@ -1042,6 +1203,7 @@ dependencies = [ "tokio", "tokio-metrics", "tokio-stream", + "tokio-tungstenite", "tokio-util", "tracing", "wildmatch", @@ -1231,6 +1393,45 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +[[package]] +name = "reqwest" +version = "0.11.23" +source = "git+https://github.com/seanmonstar/reqwest.git?branch=hyper-v1#7f4bfcbaaa8d332464709262c645893702b1ae84" +dependencies = [ + "base64 0.21.7", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-tls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "system-configuration", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + [[package]] name = "rfc7239" version = "0.1.0" @@ -1245,15 +1446,23 @@ name = "roadsign" version = "0.1.0" dependencies = [ "config", + "futures-util", + "http", + "hyper-util", "lazy_static", "poem", "poem-openapi", + "rand", + "regex", + "reqwest", "serde", "serde_json", "tokio", + "tokio-tungstenite", "toml 0.8.8", "tracing", "tracing-subscriber", + "wildmatch", ] [[package]] @@ -1311,12 +1520,44 @@ version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" +[[package]] +name = "schannel" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "security-framework" +version = "2.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "1.0.21" @@ -1419,6 +1660,15 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + [[package]] name = "slab" version = "0.4.9" @@ -1493,6 +1743,27 @@ dependencies = [ "futures-core", ] +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tempfile" version = "3.9.0" @@ -1565,6 +1836,21 @@ dependencies = [ "time-core", ] +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "tokio" version = "1.35.1" @@ -1576,7 +1862,9 @@ dependencies = [ "libc", "mio", "num_cpus", + "parking_lot", "pin-project-lite", + "signal-hook-registry", "socket2", "tokio-macros", "windows-sys 0.48.0", @@ -1605,6 +1893,16 @@ dependencies = [ "tokio-stream", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-stream" version = "0.1.14" @@ -1616,6 +1914,18 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-tungstenite" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite", +] + [[package]] name = "tokio-util" version = "0.7.10" @@ -1684,12 +1994,41 @@ dependencies = [ "winnow", ] +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + [[package]] name = "tracing" version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ + "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -1741,6 +2080,31 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "tungstenite" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand", + "sha1", + "thiserror", + "url", + "utf-8", +] + [[package]] name = "typenum" version = "1.17.0" @@ -1762,12 +2126,27 @@ dependencies = [ "version_check", ] +[[package]] +name = "unicode-bidi" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f2528f27a9eb2b21e69c95319b30bd0efd85d09c379741b0f78ea1d86be2416" + [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + [[package]] name = "universal-hash" version = "0.5.1" @@ -1784,18 +2163,50 @@ version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab4c90930b95a82d00dc9e9ac071b4991924390d46cbd0dfe566148667605e4b" +[[package]] +name = "url" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + [[package]] name = "valuable" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -1827,6 +2238,18 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bde2032aeb86bdfaecc8b261eef3cba735cc426c1f3a3416d1e0791be95fc461" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.90" @@ -1856,6 +2279,16 @@ version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b" +[[package]] +name = "web-sys" +version = "0.3.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58cd2333b6e0be7a39605f0e255892fd7418a682d8da8fe042fe25128794d2ed" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "wildmatch" version = "2.3.0" @@ -2034,6 +2467,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + [[package]] name = "yaml-rust" version = "0.4.5" diff --git a/Cargo.toml b/Cargo.toml index 510e222..38196f9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,12 +7,20 @@ edition = "2021" [dependencies] config = { version = "0.13.4", features = ["toml"] } +futures-util = "0.3.30" +http = "1.0.0" +hyper-util = { version = "0.1.2", features = ["full"] } lazy_static = "1.4.0" -poem = { version = "2.0.0", features = ["tokio-metrics"] } -poem-openapi = "4.0.0" +poem = { version = "2.0.0", features = ["tokio-metrics", "websocket"] } +poem-openapi = { version = "4.0.0", features = ["swagger-ui"] } +rand = "0.8.5" +regex = "1.10.2" +reqwest = { git = "https://github.com/seanmonstar/reqwest.git", branch = "hyper-v1", version = "0.11.23" } serde = "1.0.195" serde_json = "1.0.111" -tokio = { version = "1.35.1", features = ["rt-multi-thread", "macros", "time"] } +tokio = { version = "1.35.1", features = ["rt-multi-thread", "macros", "time", "full"] } +tokio-tungstenite = "0.21.0" toml = "0.8.8" tracing = "0.1.40" tracing-subscriber = "0.3.18" +wildmatch = "2.3.0" diff --git a/regions/index.toml b/regions/index.toml index b234b05..dbe1fe7 100644 --- a/regions/index.toml +++ b/regions/index.toml @@ -5,5 +5,5 @@ id = "root" hosts = ["localhost"] paths = ["/"] [[locations.destinations]] -id = "example" -uri = "https://example.com" +id = "echo" +uri = "https://postman-echo.com/get" diff --git a/src/main.rs b/src/main.rs index dfdea62..f7a69e1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,10 +2,12 @@ mod config; mod proxies; mod sideload; -use poem::{listener::TcpListener, Route, Server}; +use poem::{listener::TcpListener, Endpoint, EndpointExt, Route, Server}; use poem_openapi::OpenApiService; use tracing::{error, info, Level}; +use crate::proxies::route; + #[tokio::main] async fn main() -> Result<(), std::io::Error> { // Setting up logging @@ -35,10 +37,19 @@ async fn main() -> Result<(), std::io::Error> { }; // Proxies + let proxies_server = Server::new(TcpListener::bind( + config::C + .read() + .unwrap() + .get_string("listen.proxies") + .unwrap_or("0.0.0.0:80".to_string()), + )) + .run(route::handle.data(instance)); // Sideload let sideload = OpenApiService::new(sideload::SideloadApi, "Sideload API", "1.0") .server("http://localhost:3000/cgi"); + let sideload_ui = sideload.swagger_ui(); let sideload_server = Server::new(TcpListener::bind( config::C @@ -47,11 +58,13 @@ async fn main() -> Result<(), std::io::Error> { .get_string("listen.sideload") .unwrap_or("0.0.0.0:81".to_string()), )) - .run(Route::new().nest("/cgi", sideload)); + .run( + Route::new() + .nest("/cgi", sideload) + .nest("/swagger", sideload_ui), + ); - tokio::try_join!(sideload_server)?; + tokio::try_join!(proxies_server, sideload_server)?; Ok(()) } - - diff --git a/src/proxies/config.rs b/src/proxies/config.rs index a7ab6a1..2bfa414 100644 --- a/src/proxies/config.rs +++ b/src/proxies/config.rs @@ -4,23 +4,70 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Region { - id: String, - locations: Vec, + pub id: String, + pub locations: Vec, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Location { - id: String, - hosts: Vec, - paths: Vec, - headers: Option>>, - query_strings: Option>>, - destinations: Vec, + pub id: String, + pub hosts: Vec, + pub paths: Vec, + pub headers: Option>, + pub queries: Option>, + pub destinations: Vec, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Destination { - id: String, - uri: String, - timeout: Option, + pub id: String, + pub uri: String, + pub timeout: Option, + pub weight: Option, +} + +pub enum DestinationType { + Hypertext, + StaticFiles, + Unknown, +} + +impl Destination { + pub fn get_type(&self) -> DestinationType { + match self.get_protocol() { + "http" | "https" => DestinationType::Hypertext, + "file" | "files" => DestinationType::StaticFiles, + _ => DestinationType::Unknown, + } + } + + pub fn get_protocol(&self) -> &str { + self.uri.as_str().splitn(2, "://").collect::>()[0] + } + + pub fn get_queries(&self) -> &str { + self.uri.as_str().splitn(2, "?").collect::>()[1] + } + + pub fn get_host(&self) -> &str { + (self.uri.as_str().splitn(2, "://").collect::>()[1]) + .splitn(2, "?") + .collect::>()[0] + } + + pub fn get_hypertext_uri(&self) -> Result { + match self.get_protocol() { + "http" => Ok("http://".to_string() + self.get_host()), + "https" => Ok("https://".to_string() + self.get_host()), + _ => Err(()), + } + } + + pub fn get_websocket_uri(&self) -> Result { + let url = self.uri.as_str().splitn(2, "://").collect::>()[1]; + match self.get_protocol() { + "http" | "https" => Ok(url.replace("http", "ws")), + _ => Err(()), + } + } } diff --git a/src/proxies/mod.rs b/src/proxies/mod.rs index 2f5af48..3ea38bb 100644 --- a/src/proxies/mod.rs +++ b/src/proxies/mod.rs @@ -1,9 +1,13 @@ +use poem::http::{HeaderMap, Uri}; +use regex::Regex; use serde::{Deserialize, Serialize}; +use wildmatch::WildMatch; -use self::config::Region; +use self::config::{Location, Region}; pub mod config; pub mod loader; +pub mod route; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Instance { @@ -14,4 +18,44 @@ impl Instance { pub fn new() -> Instance { Instance { regions: vec![] } } + + pub fn filter(&self, uri: &Uri, headers: &HeaderMap) -> Option<&Location> { + self.regions.iter().find_map(|region| { + region.locations.iter().find(|location| { + let mut hosts = location.hosts.iter(); + if !hosts.any(|item| { + WildMatch::new(item.as_str()).matches(uri.host().unwrap_or("localhost")) + }) { + return false; + } + + let mut paths = location.paths.iter(); + if !paths.any(|item| { + uri.path().starts_with(item) + || Regex::new(item.as_str()).unwrap().is_match(uri.path()) + }) { + return false; + } + + if let Some(val) = location.headers.clone() { + match !val.keys().all(|item| { + headers.get(item).unwrap() + == location.headers.clone().unwrap().get(item).unwrap() + }) { + true => return false, + false => (), + } + }; + + if let Some(val) = location.queries.clone() { + let queries: Vec<&str> = uri.query().unwrap_or("").split('&').collect(); + if !val.iter().all(|item| queries.contains(&item.as_str())) { + return false; + } + } + + true + }) + }) + } } diff --git a/src/proxies/route.rs b/src/proxies/route.rs new file mode 100644 index 0000000..9432fc0 --- /dev/null +++ b/src/proxies/route.rs @@ -0,0 +1,155 @@ +use futures_util::{SinkExt, StreamExt}; +use poem::{ + handler, + http::{HeaderMap, StatusCode, Uri}, + web::{websocket::WebSocket, Data}, + Body, Error, FromRequest, IntoResponse, Request, Response, Result, +}; +use rand::seq::SliceRandom; +use reqwest::Method; + +use lazy_static::lazy_static; +use std::sync::Arc; +use tokio::sync::RwLock; +use tokio_tungstenite::connect_async; + +use crate::proxies::config::{Destination, DestinationType}; + +lazy_static! { + pub static ref CLIENT: reqwest::Client = reqwest::Client::new(); +} + +#[handler] +pub async fn handle( + app: Data<&super::Instance>, + req: &Request, + uri: &Uri, + headers: &HeaderMap, + method: Method, + body: Body, +) -> Result { + let location = match app.filter(uri, headers) { + Some(val) => val, + None => { + return Err(Error::from_string( + "There are no region be able to respone this request.", + StatusCode::NOT_FOUND, + )) + } + }; + + let destination = location + .destinations + .choose_weighted(&mut rand::thread_rng(), |item| item.weight.unwrap_or(1)) + .unwrap(); + + async fn forward( + end: &Destination, + req: &Request, + ori: &Uri, + headers: &HeaderMap, + method: Method, + body: Body, + ) -> Result { + // Handle websocket + if let Ok(ws) = WebSocket::from_request_without_body(req).await { + // Get uri + let Ok(uri) = end.get_websocket_uri() else { + return Err(Error::from_string( + "Proxy endpoint not configured to support websockets!", + StatusCode::NOT_IMPLEMENTED, + )); + }; + + // Build request + let mut ws_req = http::Request::builder().uri(&uri); + for (key, value) in headers.iter() { + ws_req = ws_req.header(key, value); + } + + // Start the websocket connection + return Ok(ws + .on_upgrade(move |socket| async move { + let (mut clientsink, mut clientstream) = socket.split(); + + // Start connection to server + let (serversocket, _) = connect_async(ws_req.body(()).unwrap()).await.unwrap(); + let (mut serversink, mut serverstream) = serversocket.split(); + + let client_live = Arc::new(RwLock::new(true)); + let server_live = client_live.clone(); + + tokio::spawn(async move { + while let Some(Ok(msg)) = clientstream.next().await { + match serversink.send(msg.into()).await { + Err(_) => break, + _ => {} + }; + if !*client_live.read().await { + break; + }; + } + + *client_live.write().await = false; + }); + + // Relay server messages to the client + tokio::spawn(async move { + while let Some(Ok(msg)) = serverstream.next().await { + match clientsink.send(msg.into()).await { + Err(_) => break, + _ => {} + }; + if !*server_live.read().await { + break; + }; + } + + *server_live.write().await = false; + }); + }) + .into_response()); + } + + // Handle normal web request + match end.get_type() { + DestinationType::Hypertext => { + let Ok(uri) = end.get_hypertext_uri() else { + return Err(Error::from_string( + "Proxy endpoint not configured to support web requests!", + StatusCode::NOT_IMPLEMENTED, + )); + }; + + let res = CLIENT + .request(method, uri + ori.path() + ori.query().unwrap_or("")) + .headers(headers.clone()) + .body(body.into_bytes().await.unwrap()) + .send() + .await; + + match res { + Ok(result) => { + let mut res = Response::default(); + res.extensions().clone_from(&result.extensions()); + result.headers().iter().for_each(|(key, val)| { + res.headers_mut().insert(key, val.to_owned()); + }); + res.set_status(result.status()); + res.set_version(result.version()); + res.set_body(result.bytes().await.unwrap()); + Ok(res) + } + + Err(error) => Err(Error::from_string( + error.to_string(), + error.status().unwrap_or(StatusCode::BAD_GATEWAY), + )), + } + } + _ => Err(Error::from_status(StatusCode::NOT_IMPLEMENTED)), + } + } + + forward(destination, req, uri, headers, method, body).await +}