v1.0.136: chunked upload via chunk.cfm — 12KB chunks avoid Lucee body limit

This commit is contained in:
2026-06-23 09:09:38 +04:00
parent ec842abb92
commit efc270bdf3
2 changed files with 36 additions and 8 deletions
+35 -7
View File
@@ -280,35 +280,63 @@ class Handler(BaseHTTPRequestHandler):
self.wfile.write(json.dumps({"error": str(e)}, ensure_ascii=False).encode()) self.wfile.write(json.dumps({"error": str(e)}, ensure_ascii=False).encode())
def _handle_upload(self): def _handle_upload(self):
"""Принять файл и передать в Lucee через JSON API (обходит multipart-краш Lucee).""" """Принять файл и передать в Lucee чанками (обходит лимит тела запроса)."""
import uuid
from email.parser import BytesParser from email.parser import BytesParser
content_type = self.headers.get("Content-Type", "") content_type = self.headers.get("Content-Type", "")
length = int(self.headers.get("Content-Length", 0)) length = int(self.headers.get("Content-Length", 0))
body = self.rfile.read(length) body = self.rfile.read(length)
# Парсим multipart
msg = BytesParser().parsebytes( msg = BytesParser().parsebytes(
b"Content-Type: " + content_type.encode() + b"\r\n\r\n" + body b"Content-Type: " + content_type.encode() + b"\r\n\r\n" + body
) )
filename = None filename = None
file_data = None file_data = None
contract_id = ""
if msg.is_multipart(): if msg.is_multipart():
for part in msg.get_payload(): for part in msg.get_payload():
fn = part.get_filename() fn = part.get_filename()
if fn: if fn:
filename = fn filename = fn
file_data = part.get_payload(decode=True) file_data = part.get_payload(decode=True)
break else:
# Может быть поле contract_id
disp = part.get("Content-Disposition", "")
if "name=\"contract_id\"" in disp:
contract_id = part.get_payload(decode=True).decode("utf-8", errors="ignore").strip()
if not filename or not file_data: if not filename or not file_data:
self._send_error_cors(400, "no file in request") self._send_error_cors(400, "no file in request")
return return
# Шлём в Lucee через JSON API (не multipart!) # Режем на чанки по 12KB (hex = 24KB, влезает в лимит Lucee ~50KB)
CHUNK = 12000
upload_id = str(uuid.uuid4())
raw = file_data
total = (len(raw) + CHUNK - 1) // CHUNK
for i in range(total):
chunk = raw[i*CHUNK:(i+1)*CHUNK]
r = httpx.post(
f"{LUCEE_URL}/chunk.cfm?action=put",
json={
"upload_id": upload_id,
"chunk_index": i,
"total_chunks": total,
"filename": filename,
"data": chunk.hex(),
},
timeout=30
)
if r.status_code != 200:
self._send_error_cors(502, f"chunk {i+1}/{total} failed: HTTP {r.status_code}")
return
# Собрать чанки в Lucee
resp = httpx.post( resp = httpx.post(
f"{LUCEE_URL}/upload.cfm", f"{LUCEE_URL}/chunk.cfm?action=assemble",
json={"filename": filename, "data": "\\x" + file_data.hex()}, json={"upload_id": upload_id, "contract_id": contract_id},
timeout=60 timeout=60
) )
if resp.status_code == 200: if resp.status_code == 200:
@@ -318,7 +346,7 @@ class Handler(BaseHTTPRequestHandler):
self.end_headers() self.end_headers()
self.wfile.write(resp.content) self.wfile.write(resp.content)
else: else:
self._send_error_cors(502, f"Lucee error: {resp.status_code}") self._send_error_cors(502, f"assemble failed: HTTP {resp.status_code}")
def _handle_unzip_upload(self): def _handle_unzip_upload(self):
"""Распаковать ZIP и загрузить каждый файл в Lucee. Только файлы из корня архива.""" """Распаковать ZIP и загрузить каждый файл в Lucee. Только файлы из корня архива."""
+1 -1
View File
@@ -75,7 +75,7 @@
<body> <body>
<div class="topbar"> <div class="topbar">
<img src="/nubes-logo.svg" alt="Nubes"> <img src="/nubes-logo.svg" alt="Nubes">
<span class="title">Сверка договоров — LLM AI-driven Event Sourcing <span style="font-weight:400;color:var(--muted);font-size:12px;">v1.0.134 — Lucee</span></span> <span class="title">Сверка договоров — LLM AI-driven Event Sourcing <span style="font-weight:400;color:var(--muted);font-size:12px;">v1.0.136 — Lucee</span></span>
</div> </div>
<div class="content"> <div class="content">