Add reverse proxy endpoint

This commit is contained in:
2026-01-02 00:29:56 -03:00
parent 5bb1f42bb3
commit 462a21604b
4 changed files with 166 additions and 22 deletions
Generated
+104 -18
View File
@@ -91,9 +91,12 @@ dependencies = [
"clap", "clap",
"dedent", "dedent",
"dotenvy", "dotenvy",
"futures-util",
"hyper",
"reqwest", "reqwest",
"serde", "serde",
"tokio", "tokio",
"tokio-util",
"turbostore", "turbostore",
"urlencoding", "urlencoding",
] ]
@@ -123,7 +126,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5" checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5"
dependencies = [ dependencies = [
"axum-core", "axum-core",
"bytes", "bytes 1.10.1",
"form_urlencoded", "form_urlencoded",
"futures-util", "futures-util",
"http", "http",
@@ -156,7 +159,7 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6" checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6"
dependencies = [ dependencies = [
"bytes", "bytes 1.10.1",
"futures-core", "futures-core",
"http", "http",
"http-body", "http-body",
@@ -233,6 +236,22 @@ version = "1.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422" checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422"
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "bytes"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c"
dependencies = [
"byteorder",
"iovec",
]
[[package]] [[package]]
name = "bytes" name = "bytes"
version = "1.10.1" version = "1.10.1"
@@ -419,6 +438,12 @@ dependencies = [
"percent-encoding", "percent-encoding",
] ]
[[package]]
name = "futures"
version = "0.1.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678"
[[package]] [[package]]
name = "futures-channel" name = "futures-channel"
version = "0.3.31" version = "0.3.31"
@@ -434,6 +459,23 @@ version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
[[package]]
name = "futures-io"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
[[package]]
name = "futures-macro"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "futures-sink" name = "futures-sink"
version = "0.3.31" version = "0.3.31"
@@ -453,9 +495,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"futures-io",
"futures-macro",
"futures-sink",
"futures-task", "futures-task",
"memchr",
"pin-project-lite", "pin-project-lite",
"pin-utils", "pin-utils",
"slab",
"tokio-io",
] ]
[[package]] [[package]]
@@ -500,7 +548,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785" checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785"
dependencies = [ dependencies = [
"atomic-waker", "atomic-waker",
"bytes", "bytes 1.10.1",
"fnv", "fnv",
"futures-core", "futures-core",
"futures-sink", "futures-sink",
@@ -530,7 +578,7 @@ version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565"
dependencies = [ dependencies = [
"bytes", "bytes 1.10.1",
"fnv", "fnv",
"itoa", "itoa",
] ]
@@ -541,7 +589,7 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
dependencies = [ dependencies = [
"bytes", "bytes 1.10.1",
"http", "http",
] ]
@@ -551,7 +599,7 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
dependencies = [ dependencies = [
"bytes", "bytes 1.10.1",
"futures-core", "futures-core",
"http", "http",
"http-body", "http-body",
@@ -572,13 +620,14 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]] [[package]]
name = "hyper" name = "hyper"
version = "1.6.0" version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11"
dependencies = [ dependencies = [
"bytes", "atomic-waker",
"bytes 1.10.1",
"futures-channel", "futures-channel",
"futures-util", "futures-core",
"h2", "h2",
"http", "http",
"http-body", "http-body",
@@ -586,6 +635,7 @@ dependencies = [
"httpdate", "httpdate",
"itoa", "itoa",
"pin-project-lite", "pin-project-lite",
"pin-utils",
"smallvec", "smallvec",
"tokio", "tokio",
"want", "want",
@@ -613,7 +663,7 @@ version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
dependencies = [ dependencies = [
"bytes", "bytes 1.10.1",
"http-body-util", "http-body-util",
"hyper", "hyper",
"hyper-util", "hyper-util",
@@ -630,7 +680,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f66d5bd4c6f02bf0542fad85d626775bab9258cf795a4256dcaf3161114d1df" checksum = "7f66d5bd4c6f02bf0542fad85d626775bab9258cf795a4256dcaf3161114d1df"
dependencies = [ dependencies = [
"base64", "base64",
"bytes", "bytes 1.10.1",
"futures-channel", "futures-channel",
"futures-core", "futures-core",
"futures-util", "futures-util",
@@ -801,6 +851,15 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "iovec"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "ipnet" name = "ipnet"
version = "2.11.0" version = "2.11.0"
@@ -1056,9 +1115,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531" checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531"
dependencies = [ dependencies = [
"base64", "base64",
"bytes", "bytes 1.10.1",
"encoding_rs", "encoding_rs",
"futures-core", "futures-core",
"futures-util",
"h2", "h2",
"http", "http",
"http-body", "http-body",
@@ -1080,12 +1140,14 @@ dependencies = [
"sync_wrapper", "sync_wrapper",
"tokio", "tokio",
"tokio-native-tls", "tokio-native-tls",
"tokio-util",
"tower", "tower",
"tower-http", "tower-http",
"tower-service", "tower-service",
"url", "url",
"wasm-bindgen", "wasm-bindgen",
"wasm-bindgen-futures", "wasm-bindgen-futures",
"wasm-streams",
"web-sys", "web-sys",
] ]
@@ -1396,7 +1458,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17" checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17"
dependencies = [ dependencies = [
"backtrace", "backtrace",
"bytes", "bytes 1.10.1",
"io-uring", "io-uring",
"libc", "libc",
"mio", "mio",
@@ -1407,6 +1469,17 @@ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
[[package]]
name = "tokio-io"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57fc868aae093479e3131e3d165c93b1c7474109d13c90ec0dda2a1bbfff0674"
dependencies = [
"bytes 0.4.12",
"futures",
"log",
]
[[package]] [[package]]
name = "tokio-macros" name = "tokio-macros"
version = "2.5.0" version = "2.5.0"
@@ -1440,11 +1513,11 @@ dependencies = [
[[package]] [[package]]
name = "tokio-util" name = "tokio-util"
version = "0.7.15" version = "0.7.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594"
dependencies = [ dependencies = [
"bytes", "bytes 1.10.1",
"futures-core", "futures-core",
"futures-sink", "futures-sink",
"pin-project-lite", "pin-project-lite",
@@ -1474,7 +1547,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"bytes", "bytes 1.10.1",
"futures-util", "futures-util",
"http", "http",
"http-body", "http-body",
@@ -1676,6 +1749,19 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "wasm-streams"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65"
dependencies = [
"futures-util",
"js-sys",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
]
[[package]] [[package]]
name = "web-sys" name = "web-sys"
version = "0.3.77" version = "0.3.77"
+4 -1
View File
@@ -9,8 +9,11 @@ bitcode = "0.6.6"
clap = { version = "4.5.41", features = ["derive", "env"] } clap = { version = "4.5.41", features = ["derive", "env"] }
dedent = "0.1.1" dedent = "0.1.1"
dotenvy = "0.15.7" dotenvy = "0.15.7"
reqwest = { version = "0.12.22", features = ["json"] } futures-util = { version = "0.3.31", features = ["tokio-io"] }
hyper = "1.8.1"
reqwest = { version = "0.12.22", features = ["json", "stream"] }
serde = { version = "1.0.219", features = ["derive"] } serde = { version = "1.0.219", features = ["derive"] }
tokio = { version = "1.46.1", features = ["macros", "rt-multi-thread"] } tokio = { version = "1.46.1", features = ["macros", "rt-multi-thread"] }
tokio-util = { version = "0.7.17", features = ["io"] }
turbostore = "0.2.0" turbostore = "0.2.0"
urlencoding = "2.1.3" urlencoding = "2.1.3"
+12 -2
View File
@@ -18,8 +18,9 @@ async fn docs() -> &'static str {
dedent!( dedent!(
" "
Welcome to Nyeki's API! This is a tiny API I built to provide some basic information I Welcome to Nyeki's API! This is a tiny API I built to provide some basic information I
found myself needing in more than one place. This at the moment only includes Discord and found myself needing in more than one place. This at the moment only includes Discord
GitHub profiles, but I may add more stuff as I see myself needing it. profiles, GitHub profiles, and a reverse proxy, but I may add more stuff as I see myself
needing it.
The API is versioned, so API endpoints will always be prefixed with `/v{version}`. The The API is versioned, so API endpoints will always be prefixed with `/v{version}`. The
current version is 1 (i.e. stuff is prefixed by `/v1`). All responses are JSON (or current version is 1 (i.e. stuff is prefixed by `/v1`). All responses are JSON (or
@@ -88,6 +89,15 @@ async fn docs() -> &'static str {
for supported sources). for supported sources).
- `source`: The source of the profile (either `discord` or `github`). - `source`: The source of the profile (either `discord` or `github`).
GET /v1/proxy
Proxies a request to the given URL.
Note that headers are not forwarded and neither is the request body. This is a
simple GET proxy.
Query parameters:
- `url`: The URL to proxy the request to.
They all take 2 query parameters: `id` and `source`. The `id` is the ID of the user you They all take 2 query parameters: `id` and `source`. The `id` is the ID of the user you
want to get the profile of, and the `source` is the source of the profile. The source can want to get the profile of, and the `source` is the source of the profile. The source can
be either `discord` or `github`. be either `discord` or `github`.
+46 -1
View File
@@ -4,10 +4,12 @@ use std::sync::Arc;
use axum::{ use axum::{
Json, Router, Json, Router,
body::{Body, Bytes},
extract::{Query, State as AxumState}, extract::{Query, State as AxumState},
response::Redirect, response::{Redirect, Response},
routing::get, routing::get,
}; };
use futures_util::StreamExt;
use reqwest::Client; use reqwest::Client;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use turbostore::{Decode, Duration, Encode}; use turbostore::{Decode, Duration, Encode};
@@ -21,6 +23,7 @@ pub fn router() -> Router<Arc<State>> {
.route("/profile/avatar", get(get_profile_avatar)) .route("/profile/avatar", get(get_profile_avatar))
.route("/profile/banner", get(get_profile_banner)) .route("/profile/banner", get(get_profile_banner))
.route("/profile/url", get(get_profile_url)) .route("/profile/url", get(get_profile_url))
.route("/proxy", get(proxy_with_cors))
.fallback(error_404) .fallback(error_404)
.method_not_allowed_fallback(error_405) .method_not_allowed_fallback(error_405)
} }
@@ -300,3 +303,45 @@ async fn get_profile_url(
Ok(Redirect::to(&profile.url)) Ok(Redirect::to(&profile.url))
} }
#[derive(Debug, Deserialize)]
struct ProxyQuery {
url: String,
}
async fn proxy_with_cors(Query(params): Query<ProxyQuery>) -> Result<Response, Error> {
let client = Client::new();
match client.get(&params.url).send().await {
Ok(resp) => {
let status = resp.status();
let headers = resp.headers().clone();
// Turn the reqwest response into a streaming body
let stream = resp.bytes_stream().map(|chunk_result| match chunk_result {
Ok(chunk) => Ok::<Bytes, std::io::Error>(chunk),
Err(_) => Err(std::io::Error::other("stream error")),
});
// Convert the stream into a hyper Body
let body = Body::from_stream(stream);
// Build response
let mut response = Response::builder().status(status);
let headers_mut = response.headers_mut().unwrap();
for (key, value) in headers.iter() {
// Skip certain headers that hyper will manage itself
if key.as_str().eq_ignore_ascii_case("content-length") {
continue;
}
headers_mut.insert(key, value.clone());
}
Ok(response.body(body).unwrap())
}
Err(_) => Err(Error {
status: 500,
message: "Failed to fetch the requested URL".into(),
}),
}
}