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
+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(),
}),
}
}