perf: offload image decode/resize/encode to spawn_blocking
This commit is contained in:
parent
b48590ced3
commit
fe6e5bd045
103
src/api/image.rs
103
src/api/image.rs
@ -267,6 +267,43 @@ fn process_image(
|
|||||||
Ok((buf.into_inner(), ct))
|
Ok((buf.into_inner(), ct))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
fn process_image_blocking(
|
||||||
|
data: Vec<u8>,
|
||||||
|
params: ImageParams,
|
||||||
|
path: String,
|
||||||
|
) -> Result<(Vec<u8>, HeaderValue), StatusCode> {
|
||||||
|
let original_format = detect_format(&path);
|
||||||
|
|
||||||
|
let img = if original_format == image::ImageFormat::WebP {
|
||||||
|
match crate::webp::decode(&data) {
|
||||||
|
Ok(img) => {
|
||||||
|
check_image_dimensions(img.width(), img.height())?;
|
||||||
|
img
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
tracing::warn!("WebP decode failed ({}), returning raw bytes", e);
|
||||||
|
let ct = content_type(original_format);
|
||||||
|
return Ok((data, ct));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let cursor = std::io::Cursor::new(&data);
|
||||||
|
let mut reader = image::ImageReader::with_format(cursor, original_format);
|
||||||
|
reader.limits(image_reader_limits());
|
||||||
|
match reader.decode() {
|
||||||
|
Ok(img) => img,
|
||||||
|
Err(e) => {
|
||||||
|
tracing::warn!("Image decode failed ({}), returning raw bytes", e);
|
||||||
|
let ct = content_type(original_format);
|
||||||
|
return Ok((data, ct));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
process_image(img, ¶ms, original_format)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
fn is_path_safe(path: &str) -> bool {
|
fn is_path_safe(path: &str) -> bool {
|
||||||
// Reject paths with parent directory references or null bytes
|
// Reject paths with parent directory references or null bytes
|
||||||
@ -399,39 +436,23 @@ pub async fn serve_image(
|
|||||||
Err(_) => return StatusCode::NOT_FOUND.into_response(),
|
Err(_) => return StatusCode::NOT_FOUND.into_response(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// WebP 解码使用 zenwebp,其它格式使用 image crate。
|
// Offload decode + resize + encode to the blocking pool so the async
|
||||||
let original_format = detect_format(&path);
|
// runtime stays responsive to other requests.
|
||||||
let img = if original_format == image::ImageFormat::WebP {
|
let data_for_blocking = data.clone();
|
||||||
match crate::webp::decode(&data) {
|
let path_for_blocking = path.clone();
|
||||||
Ok(img) => {
|
let params_for_blocking = params.clone();
|
||||||
if let Err(status) = check_image_dimensions(img.width(), img.height()) {
|
let (processed, content_type) =
|
||||||
return status.into_response();
|
match tokio::task::spawn_blocking(move || {
|
||||||
|
process_image_blocking(data_for_blocking, params_for_blocking, path_for_blocking)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(Ok(r)) => r,
|
||||||
|
Ok(Err(status)) => return status.into_response(),
|
||||||
|
Err(_) => {
|
||||||
|
tracing::error!("Image processing task panicked");
|
||||||
|
return StatusCode::INTERNAL_SERVER_ERROR.into_response();
|
||||||
}
|
}
|
||||||
img
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
tracing::warn!("WebP decode failed ({}), returning raw bytes", e);
|
|
||||||
let ct = content_type(original_format);
|
|
||||||
return (StatusCode::OK, [(header::CONTENT_TYPE, ct)], data).into_response();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let cursor = std::io::Cursor::new(&data);
|
|
||||||
let mut reader = image::ImageReader::with_format(cursor, original_format);
|
|
||||||
reader.limits(image_reader_limits());
|
|
||||||
match reader.decode() {
|
|
||||||
Ok(img) => img,
|
|
||||||
Err(e) => {
|
|
||||||
tracing::warn!("Image decode failed ({}), returning raw bytes", e);
|
|
||||||
let ct = content_type(original_format);
|
|
||||||
return (StatusCode::OK, [(header::CONTENT_TYPE, ct)], data).into_response();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let (processed, content_type) = match process_image(img, ¶ms, original_format) {
|
|
||||||
Ok(r) => r,
|
|
||||||
Err(status) => return status.into_response(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let cached = CachedImage {
|
let cached = CachedImage {
|
||||||
@ -673,4 +694,22 @@ mod tests {
|
|||||||
let base2 = disk_cache_base("path|w=1200");
|
let base2 = disk_cache_base("path|w=1200");
|
||||||
assert_ne!(base1, base2);
|
assert_ne!(base1, base2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn process_image_blocking_resizes_png() {
|
||||||
|
let img = image::DynamicImage::new_rgb8(100, 100);
|
||||||
|
let mut buf = std::io::Cursor::new(Vec::new());
|
||||||
|
img.write_to(&mut buf, image::ImageFormat::Png).unwrap();
|
||||||
|
let data = buf.into_inner();
|
||||||
|
|
||||||
|
let params = ImageParams {
|
||||||
|
w: Some(50),
|
||||||
|
format: Some("webp".to_string()),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let (out, ct) = process_image_blocking(data, params, "test.png".to_string()).unwrap();
|
||||||
|
assert!(!out.is_empty());
|
||||||
|
assert_eq!(ct, HeaderValue::from_static("image/webp"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user