5x request size limit for raw app bundle uploads (#8640)

* feat: 5x request size limit for raw app bundle uploads

Raw app bundle endpoints (create_raw, update_raw) now get 5x the
configured request size limit. Also improves error messages when
multipart uploads exceed the limit to include the actual limit
and mention it's adjustable in instance settings.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: mention size limit as possible cause, not definitive

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ruben Fiszel
2026-03-31 11:14:20 +00:00
committed by GitHub
parent b69d63eeb0
commit cf30bcf3f9
2 changed files with 16 additions and 6 deletions
+13 -5
View File
@@ -79,7 +79,7 @@ use windmill_common::{jwt, oauth2::HmacSha256, variables::get_workspace_key};
#[cfg(feature = "parquet")]
use windmill_types::s3::{S3Object, S3Permission};
pub fn workspaced_service() -> Router {
pub fn workspaced_service(raw_app_body_limit: usize) -> Router {
Router::new()
.route("/list", get(list_apps))
.route("/list_search", get(list_search_apps))
@@ -95,10 +95,16 @@ pub fn workspaced_service() -> Router {
.route("/get_data/v/{*id}", get(get_raw_app_data))
.route("/exists/{*path}", get(exists_app))
.route("/update/{*path}", post(update_app))
.route("/update_raw/{*path}", post(update_app_raw))
.route(
"/update_raw/{*path}",
post(update_app_raw).layer(axum::extract::DefaultBodyLimit::max(raw_app_body_limit)),
)
.route("/delete/{*path}", delete(delete_app))
.route("/create", post(create_app))
.route("/create_raw", post(create_app_raw))
.route(
"/create_raw",
post(create_app_raw).layer(axum::extract::DefaultBodyLimit::max(raw_app_body_limit)),
)
.route("/history/p/{*path}", get(get_app_history))
.route("/get_latest_version/{*path}", get(get_latest_version))
.route(
@@ -1010,18 +1016,20 @@ macro_rules! process_app_multipart {
let mut saved_app = None;
let mut uploaded_js = false;
let request_size_limit_mb = *crate::REQUEST_SIZE_LIMIT.read().await / (1024 * 1024);
let raw_app_limit_mb = request_size_limit_mb * 5;
let mut multipart = $multipart;
while let Some(field) = multipart
.next_field()
.await
.map_err(|e| Error::BadRequest(format!("failed to read multipart field: {e}")))?
.map_err(|e| Error::BadRequest(format!("failed to read multipart field: {e}. Could be due to the request size limit for raw app bundles which is {raw_app_limit_mb}MB (adjustable in instance settings)")))?
{
let name = field
.name()
.ok_or_else(|| Error::BadRequest("multipart field missing name".to_string()))?
.to_string();
let data = field.bytes().await.map_err(|e| {
Error::BadRequest(format!("failed to read multipart stream: {e}"))
Error::BadRequest(format!("failed to read multipart stream: {e}. Could be due to the request size limit for raw app bundles which is {raw_app_limit_mb}MB (adjustable in instance settings)"))
})?;
if name == "app" {
let app = serde_json::from_slice(&data).map_err(to_anyhow)?;
+3 -1
View File
@@ -379,6 +379,8 @@ pub async fn run_server(
REQUEST_SIZE_LIMIT.read().await.clone(),
));
let request_size_limit = REQUEST_SIZE_LIMIT.read().await.clone();
let cors = CorsLayer::new()
.allow_methods([http::Method::GET, http::Method::POST, http::Method::DELETE])
.allow_headers([http::header::CONTENT_TYPE, http::header::AUTHORIZATION])
@@ -533,7 +535,7 @@ pub async fn run_server(
Router::new()
// Reordered alphabetically
.nest("/acls", granular_acls::workspaced_service())
.nest("/apps", apps::workspaced_service())
.nest("/apps", apps::workspaced_service(request_size_limit * 5))
.nest("/assets", windmill_api_assets::workspaced_service())
.nest("/audit", audit::workspaced_service())
.nest("/capture", capture::workspaced_service())