扩大原始图片文本字段上限

multipart 文本字段改为 chunk 流式读取并限制 16KB。

统一 UTF-8 校验和超限错误,避免先完整分配超长 prompt。
This commit is contained in:
2026-09-11 17:28:13 +08:00
parent 46df12c086
commit 48c5638893
+24 -7
View File
@@ -58,7 +58,7 @@ pub(crate) struct RawImageEditResponse {
pub(crate) data: Vec<RawImageEditItem>,
}
const RAW_IMAGE_MAX_PROMPT_BYTES: usize = 4 * 1024;
const RAW_IMAGE_MAX_TEXT_FIELD_BYTES: usize = 16 * 1024;
pub(crate) async fn edit_raw_image(
State(state): State<AppState>,
@@ -244,15 +244,32 @@ async fn read_multipart_image(
async fn set_text_field(
target: &mut Option<String>,
field: axum::extract::multipart::Field<'_>,
mut field: axum::extract::multipart::Field<'_>,
name: &str,
) -> Result<(), AppError> {
if target.is_some() {
return Err(bad_request(format!("{name} 字段不能重复")));
}
*target = Some(field.text().await.map_err(|error| {
let mut bytes = Vec::new();
while let Some(chunk) = field.chunk().await.map_err(|error| {
tracing::warn!(field = name, error = %error, "raw image multipart 文本读取失败");
bad_request(format!("{name} 字段读取失败"))
})? {
if bytes.len().saturating_add(chunk.len()) > RAW_IMAGE_MAX_TEXT_FIELD_BYTES {
tracing::warn!(
field = name,
limit_bytes = RAW_IMAGE_MAX_TEXT_FIELD_BYTES,
"raw image multipart 文本字段超过大小限制"
);
return Err(bad_request(format!(
"{name} 字段不能超过 {RAW_IMAGE_MAX_TEXT_FIELD_BYTES} 字节"
)));
}
bytes.extend_from_slice(&chunk);
}
*target = Some(String::from_utf8(bytes).map_err(|error| {
tracing::warn!(field = name, error = %error, "raw image multipart 文本字段不是有效 UTF-8");
bad_request(format!("{name} 字段必须为有效 UTF-8 文本"))
})?);
Ok(())
}
@@ -269,9 +286,9 @@ fn prepare_request(payload: RawImageEditRequest) -> Result<PreparedRawImageEdit,
if payload.prompt.trim().is_empty() {
return Err(bad_request("prompt 不能为空"));
}
if payload.prompt.len() > RAW_IMAGE_MAX_PROMPT_BYTES {
if payload.prompt.len() > RAW_IMAGE_MAX_TEXT_FIELD_BYTES {
return Err(bad_request(format!(
"prompt 不能超过 {RAW_IMAGE_MAX_PROMPT_BYTES} 字节"
"prompt 不能超过 {RAW_IMAGE_MAX_TEXT_FIELD_BYTES} 字节"
)));
}
validate_raw_image_edit_dimensions(payload.width, payload.height)
@@ -567,12 +584,12 @@ mod tests {
#[test]
fn prompt_uses_raw_utf8_byte_limit() {
let mut parsed = request(png_bytes(1, 1), None);
parsed.prompt = "a".repeat(RAW_IMAGE_MAX_PROMPT_BYTES + 1);
parsed.prompt = "a".repeat(RAW_IMAGE_MAX_TEXT_FIELD_BYTES + 1);
let error = match prepare_request(parsed) {
Ok(_) => panic!("oversized prompt should fail before image decode and billing"),
Err(error) => error,
};
assert!(format!("{error:?}").contains("prompt 不能超过 4096 字节"));
assert!(format!("{error:?}").contains("prompt 不能超过 16384 字节"));
}
}