Add reverse proxy endpoint
This commit is contained in:
+12
-2
@@ -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
@@ -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(¶ms.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(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user