扩大原始图片文本字段上限
multipart 文本字段改为 chunk 流式读取并限制 16KB。 统一 UTF-8 校验和超限错误,避免先完整分配超长 prompt。
This commit is contained in:
@@ -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 字节"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user