v1.0.136: chunked upload via chunk.cfm — 12KB chunks avoid Lucee body limit
This commit is contained in:
@@ -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. Только файлы из корня архива."""
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
Reference in New Issue
Block a user