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",
"dedent",
"dotenvy",
"futures-util",
"hyper",
"reqwest",
"serde",
"tokio",
"tokio-util",
"turbostore",
"urlencoding",
]
@@ -123,7 +126,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5"
dependencies = [
"axum-core",
"bytes",
"bytes 1.10.1",
"form_urlencoded",
"futures-util",
"http",
@@ -156,7 +159,7 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6"
dependencies = [
"bytes",
"bytes 1.10.1",
"futures-core",
"http",
"http-body",
@@ -233,6 +236,22 @@ version = "1.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "bytes"
version = "1.10.1"
@@ -419,6 +438,12 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "futures"
version = "0.1.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678"
[[package]]
name = "futures-channel"
version = "0.3.31"
@@ -434,6 +459,23 @@ version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "futures-sink"
version = "0.3.31"
@@ -453,9 +495,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
dependencies = [
"futures-core",
"futures-io",
"futures-macro",
"futures-sink",
"futures-task",
"memchr",
"pin-project-lite",
"pin-utils",
"slab",
"tokio-io",
]
[[package]]
@@ -500,7 +548,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785"
dependencies = [
"atomic-waker",
"bytes",
"bytes 1.10.1",
"fnv",
"futures-core",
"futures-sink",
@@ -530,7 +578,7 @@ version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565"
dependencies = [
"bytes",
"bytes 1.10.1",
"fnv",
"itoa",
]
@@ -541,7 +589,7 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
dependencies = [
"bytes",
"bytes 1.10.1",
"http",
]
@@ -551,7 +599,7 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
dependencies = [
"bytes",
"bytes 1.10.1",
"futures-core",
"http",
"http-body",
@@ -572,13 +620,14 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]]
name = "hyper"
version = "1.6.0"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80"
checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11"
dependencies = [
"bytes",
"atomic-waker",
"bytes 1.10.1",
"futures-channel",
"futures-util",
"futures-core",
"h2",
"http",
"http-body",
@@ -586,6 +635,7 @@ dependencies = [
"httpdate",
"itoa",
"pin-project-lite",
"pin-utils",
"smallvec",
"tokio",
"want",
@@ -613,7 +663,7 @@ version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
dependencies = [
"bytes",
"bytes 1.10.1",
"http-body-util",
"hyper",
"hyper-util",
@@ -630,7 +680,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f66d5bd4c6f02bf0542fad85d626775bab9258cf795a4256dcaf3161114d1df"
dependencies = [
"base64",
"bytes",
"bytes 1.10.1",
"futures-channel",
"futures-core",
"futures-util",
@@ -801,6 +851,15 @@ dependencies = [
"libc",
]
[[package]]
name = "iovec"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e"
dependencies = [
"libc",
]
[[package]]
name = "ipnet"
version = "2.11.0"
@@ -1056,9 +1115,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531"
dependencies = [
"base64",
"bytes",
"bytes 1.10.1",
"encoding_rs",
"futures-core",
"futures-util",
"h2",
"http",
"http-body",
@@ -1080,12 +1140,14 @@ dependencies = [
"sync_wrapper",
"tokio",
"tokio-native-tls",
"tokio-util",
"tower",
"tower-http",
"tower-service",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"wasm-streams",
"web-sys",
]
@@ -1396,7 +1458,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17"
dependencies = [
"backtrace",
"bytes",
"bytes 1.10.1",
"io-uring",
"libc",
"mio",
@@ -1407,6 +1469,17 @@ dependencies = [
"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]]
name = "tokio-macros"
version = "2.5.0"
@@ -1440,11 +1513,11 @@ dependencies = [
[[package]]
name = "tokio-util"
version = "0.7.15"
version = "0.7.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df"
checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594"
dependencies = [
"bytes",
"bytes 1.10.1",
"futures-core",
"futures-sink",
"pin-project-lite",
@@ -1474,7 +1547,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2"
dependencies = [
"bitflags",
"bytes",
"bytes 1.10.1",
"futures-util",
"http",
"http-body",
@@ -1676,6 +1749,19 @@ dependencies = [
"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]]
name = "web-sys"
version = "0.3.77"
+4 -1
View File
@@ -9,8 +9,11 @@ bitcode = "0.6.6"
clap = { version = "4.5.41", features = ["derive", "env"] }
dedent = "0.1.1"
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"] }
tokio = { version = "1.46.1", features = ["macros", "rt-multi-thread"] }
tokio-util = { version = "0.7.17", features = ["io"] }
turbostore = "0.2.0"
urlencoding = "2.1.3"
+12 -2
View File
@@ -18,8 +18,9 @@ async fn docs() -> &'static str {
dedent!(
"
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
GitHub profiles, but I may add more stuff as I see myself needing it.
found myself needing in more than one place. This at the moment only includes Discord
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
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).
- `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
want to get the profile of, and the `source` is the source of the profile. The source can
be either `discord` or `github`.
+46 -1
View File
@@ -4,10 +4,12 @@ use std::sync::Arc;
use axum::{
Json, Router,
body::{Body, Bytes},
extract::{Query, State as AxumState},
response::Redirect,
response::{Redirect, Response},
routing::get,
};
use futures_util::StreamExt;
use reqwest::Client;
use serde::{Deserialize, Serialize};
use turbostore::{Decode, Duration, Encode};
@@ -21,6 +23,7 @@ pub fn router() -> Router<Arc<State>> {
.route("/profile/avatar", get(get_profile_avatar))
.route("/profile/banner", get(get_profile_banner))
.route("/profile/url", get(get_profile_url))
.route("/proxy", get(proxy_with_cors))
.fallback(error_404)
.method_not_allowed_fallback(error_405)
}
@@ -300,3 +303,45 @@ async fn get_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(),
}),
}
}