From 05843fb7b5d6d1808b2473274eb84bc503d47641 Mon Sep 17 00:00:00 2001 From: Felix Ableitner Date: Thu, 12 Dec 2024 16:06:51 +0100 Subject: [PATCH] Move into subfolders --- .../routes/src/{images.rs => images/mod.rs} | 68 ++----------------- crates/routes/src/images/utils.rs | 63 +++++++++++++++++ .../utils/src/utils/markdown/image_links.rs | 12 ++-- src/api_routes_v3.rs | 2 +- src/api_routes_v4.rs | 2 +- 5 files changed, 78 insertions(+), 69 deletions(-) rename crates/routes/src/{images.rs => images/mod.rs} (82%) create mode 100644 crates/routes/src/images/utils.rs diff --git a/crates/routes/src/images.rs b/crates/routes/src/images/mod.rs similarity index 82% rename from crates/routes/src/images.rs rename to crates/routes/src/images/mod.rs index dc981f617..0f98b330e 100644 --- a/crates/routes/src/images.rs +++ b/crates/routes/src/images/mod.rs @@ -2,7 +2,6 @@ use actix_web::{ body::{BodyStream, BoxBody}, http::{ header::{HeaderName, ACCEPT_ENCODING, HOST}, - Method, StatusCode, }, web::*, @@ -10,8 +9,6 @@ use actix_web::{ HttpResponse, Responder, }; -use futures::stream::{Stream, StreamExt}; -use http::HeaderValue; use lemmy_api_common::{context::LemmyContext, request::PictrsResponse}; use lemmy_db_schema::source::{ images::{LocalImage, LocalImageForm, RemoteImage}, @@ -24,6 +21,9 @@ use reqwest_middleware::RequestBuilder; use serde::Deserialize; use std::time::Duration; use url::Url; +use utils::{convert_header, convert_method, convert_status, make_send}; + +mod utils; trait ProcessUrl { /// If thumbnail or format is given, this uses the pictrs process endpoint. @@ -223,7 +223,10 @@ pub async fn delete_image( Ok(HttpResponse::build(convert_status(res.status())).body(BodyStream::new(res.bytes_stream()))) } -pub async fn pictrs_healthz(req: HttpRequest, context: Data) -> LemmyResult { +pub async fn pictrs_healthz( + req: HttpRequest, + context: Data, +) -> LemmyResult { let pictrs_config = context.settings().pictrs_config()?; let url = format!("{}healthz", pictrs_config.url); @@ -264,60 +267,3 @@ pub async fn image_proxy( Ok(Either::Right(image(processed_url, req, &context).await?)) } } - -fn make_send(mut stream: S) -> impl Stream + Send + Unpin + 'static -where - S: Stream + Unpin + 'static, - S::Item: Send, -{ - // NOTE: the 8 here is arbitrary - let (tx, rx) = tokio::sync::mpsc::channel(8); - - // NOTE: spawning stream into a new task can potentially hit this bug: - // - https://github.com/actix/actix-web/issues/1679 - // - // Since 4.0.0-beta.2 this issue is incredibly less frequent. I have not personally reproduced it. - // That said, it is still technically possible to encounter. - actix_web::rt::spawn(async move { - while let Some(res) = stream.next().await { - if tx.send(res).await.is_err() { - break; - } - } - }); - - SendStream { rx } -} - -struct SendStream { - rx: tokio::sync::mpsc::Receiver, -} - -impl Stream for SendStream -where - T: Send, -{ - type Item = T; - - fn poll_next( - mut self: std::pin::Pin<&mut Self>, - cx: &mut std::task::Context<'_>, - ) -> std::task::Poll> { - std::pin::Pin::new(&mut self.rx).poll_recv(cx) - } -} - -// TODO: remove these conversions after actix-web upgrades to http 1.0 -#[allow(clippy::expect_used)] -fn convert_status(status: http::StatusCode) -> StatusCode { - StatusCode::from_u16(status.as_u16()).expect("status can be converted") -} - -#[allow(clippy::expect_used)] -fn convert_method(method: &Method) -> http::Method { - http::Method::from_bytes(method.as_str().as_bytes()).expect("method can be converted") -} - -fn convert_header<'a>(name: &'a http::HeaderName, value: &'a HeaderValue) -> (&'a str, &'a [u8]) { - (name.as_str(), value.as_bytes()) -} diff --git a/crates/routes/src/images/utils.rs b/crates/routes/src/images/utils.rs new file mode 100644 index 000000000..c951b4b01 --- /dev/null +++ b/crates/routes/src/images/utils.rs @@ -0,0 +1,63 @@ +use actix_web::http::{Method, StatusCode}; +use futures::stream::{Stream, StreamExt}; +use http::HeaderValue; + +pub(super) fn make_send(mut stream: S) -> impl Stream + Send + Unpin + 'static +where + S: Stream + Unpin + 'static, + S::Item: Send, +{ + // NOTE: the 8 here is arbitrary + let (tx, rx) = tokio::sync::mpsc::channel(8); + + // NOTE: spawning stream into a new task can potentially hit this bug: + // - https://github.com/actix/actix-web/issues/1679 + // + // Since 4.0.0-beta.2 this issue is incredibly less frequent. I have not personally reproduced it. + // That said, it is still technically possible to encounter. + actix_web::rt::spawn(async move { + while let Some(res) = stream.next().await { + if tx.send(res).await.is_err() { + break; + } + } + }); + + SendStream { rx } +} + +pub(super) struct SendStream { + rx: tokio::sync::mpsc::Receiver, +} + +impl Stream for SendStream +where + T: Send, +{ + type Item = T; + + fn poll_next( + mut self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + std::pin::Pin::new(&mut self.rx).poll_recv(cx) + } +} + +// TODO: remove these conversions after actix-web upgrades to http 1.0 +#[allow(clippy::expect_used)] +pub(super) fn convert_status(status: http::StatusCode) -> StatusCode { + StatusCode::from_u16(status.as_u16()).expect("status can be converted") +} + +#[allow(clippy::expect_used)] +pub(super) fn convert_method(method: &Method) -> http::Method { + http::Method::from_bytes(method.as_str().as_bytes()).expect("method can be converted") +} + +pub(super) fn convert_header<'a>( + name: &'a http::HeaderName, + value: &'a HeaderValue, +) -> (&'a str, &'a [u8]) { + (name.as_str(), value.as_bytes()) +} diff --git a/crates/utils/src/utils/markdown/image_links.rs b/crates/utils/src/utils/markdown/image_links.rs index 0990b1bc7..2185bdcad 100644 --- a/crates/utils/src/utils/markdown/image_links.rs +++ b/crates/utils/src/utils/markdown/image_links.rs @@ -18,7 +18,7 @@ pub fn markdown_rewrite_image_links(mut src: String) -> (String, Vec) { // If link points to remote domain, replace with proxied link if parsed.domain() != Some(&SETTINGS.hostname) { let mut proxied = format!( - "{}/api/v4/image_proxy?url={}", + "{}/api/v4/image/proxy?url={}", SETTINGS.get_protocol_and_hostname(), encode(url), ); @@ -115,7 +115,7 @@ mod tests { ( "remote image proxied", "![link](http://example.com/image.jpg)", - "![link](https://lemmy-alpha/api/v4/image_proxy?url=http%3A%2F%2Fexample.com%2Fimage.jpg)", + "![link](https://lemmy-alpha/api/v4/image/proxy?url=http%3A%2F%2Fexample.com%2Fimage.jpg)", ), ( "local image unproxied", @@ -125,7 +125,7 @@ mod tests { ( "multiple image links", "![link](http://example.com/image1.jpg) ![link](http://example.com/image2.jpg)", - "![link](https://lemmy-alpha/api/v4/image_proxy?url=http%3A%2F%2Fexample.com%2Fimage1.jpg) ![link](https://lemmy-alpha/api/v4/image_proxy?url=http%3A%2F%2Fexample.com%2Fimage2.jpg)", + "![link](https://lemmy-alpha/api/v4/image/proxy?url=http%3A%2F%2Fexample.com%2Fimage1.jpg) ![link](https://lemmy-alpha/api/v4/image/proxy?url=http%3A%2F%2Fexample.com%2Fimage2.jpg)", ), ( "empty link handled", @@ -135,7 +135,7 @@ mod tests { ( "empty label handled", "![](http://example.com/image.jpg)", - "![](https://lemmy-alpha/api/v4/image_proxy?url=http%3A%2F%2Fexample.com%2Fimage.jpg)" + "![](https://lemmy-alpha/api/v4/image/proxy?url=http%3A%2F%2Fexample.com%2Fimage.jpg)" ), ( "invalid image link removed", @@ -145,12 +145,12 @@ mod tests { ( "label with nested markdown handled", "![a *b* c](http://example.com/image.jpg)", - "![a *b* c](https://lemmy-alpha/api/v4/image_proxy?url=http%3A%2F%2Fexample.com%2Fimage.jpg)" + "![a *b* c](https://lemmy-alpha/api/v4/image/proxy?url=http%3A%2F%2Fexample.com%2Fimage.jpg)" ), ( "custom emoji support", r#"![party-blob](https://www.hexbear.net/pictrs/image/83405746-0620-4728-9358-5f51b040ffee.gif "emoji party-blob")"#, - r#"![party-blob](https://lemmy-alpha/api/v4/image_proxy?url=https%3A%2F%2Fwww.hexbear.net%2Fpictrs%2Fimage%2F83405746-0620-4728-9358-5f51b040ffee.gif "emoji party-blob")"# + r#"![party-blob](https://lemmy-alpha/api/v4/image/proxy?url=https%3A%2F%2Fwww.hexbear.net%2Fpictrs%2Fimage%2F83405746-0620-4728-9358-5f51b040ffee.gif "emoji party-blob")"# ) ]; diff --git a/src/api_routes_v3.rs b/src/api_routes_v3.rs index 56dde6db9..452271dd1 100644 --- a/src/api_routes_v3.rs +++ b/src/api_routes_v3.rs @@ -139,7 +139,7 @@ use lemmy_utils::rate_limit::RateLimitCell; // Deprecated, use api v4 instead. // When removing api v3, we also need to rewrite all links in database with -// `/api/v3/image_proxy` to use `/api/v4/image_proxy` instead. +// `/api/v3/image_proxy` to use `/api/v4/image/proxy` instead. pub fn config(cfg: &mut ServiceConfig, rate_limit: &RateLimitCell) { cfg .service( diff --git a/src/api_routes_v4.rs b/src/api_routes_v4.rs index ee88274c9..48f4031d4 100644 --- a/src/api_routes_v4.rs +++ b/src/api_routes_v4.rs @@ -172,7 +172,6 @@ pub fn config(cfg: &mut ServiceConfig, rate_limit: &RateLimitCell) { cfg.service( scope("/api/v4") .wrap(rate_limit.message()) - .route("/image_proxy", get().to(image_proxy)) // Site .service( scope("/site") @@ -401,6 +400,7 @@ pub fn config(cfg: &mut ServiceConfig, rate_limit: &RateLimitCell) { .wrap(rate_limit.image()) .route(post().to(upload_image)), ) + .route("/proxy", get().to(image_proxy)) .route("/{filename}", get().to(get_full_res_image)) .route("{token}/{filename}", delete().to(delete_image)) .route("/healthz", get().to(pictrs_healthz)),