From a6b3e267b1a53dd4c5fa681f88b75c9493a84bb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CNaeel=E2=80=9D?= Date: Sat, 11 Jul 2026 10:01:06 +0400 Subject: [PATCH] feat: WS ACK + 8KB chunks (below kube-vip 50KB threshold), bump 1.0.21 --- site/app.py | 5 ++- site/static/app.js | 103 ++++++++++++++++++++++++++++----------------- 2 files changed, 67 insertions(+), 41 deletions(-) diff --git a/site/app.py b/site/app.py index c3874b4..463b9d7 100644 --- a/site/app.py +++ b/site/app.py @@ -6,7 +6,7 @@ from flask_sock import Sock app = Flask(__name__) sock = Sock(app) -VERSION = '1.0.20' +VERSION = '1.0.21' @app.route('/') @@ -21,7 +21,7 @@ def health(): @sock.route('/ws-upload') def ws_upload(ws): - """WebSocket: получает чанки, считает байты и время""" + """WebSocket: получает чанки, ACK после каждого""" t0 = time.time() total = 0 while True: @@ -31,6 +31,7 @@ def ws_upload(ws): if isinstance(chunk, str) and chunk == 'DONE': break total += len(chunk) + ws.send(json.dumps({'ack': total})) elapsed = round((time.time() - t0) * 1000) ws.send(json.dumps({'ok': True, 'bytes': total, 'elapsed_ms': elapsed})) diff --git a/site/static/app.js b/site/static/app.js index 5d0afd9..12fd1d3 100644 --- a/site/static/app.js +++ b/site/static/app.js @@ -155,7 +155,10 @@ async function uploadAll() { var okCount = 0; var errCount = 0; - // HTTP POST на /upload (base64, с retry) + // WebSocket с 8KB чанками и server ACK + var proto = location.protocol === 'https:' ? 'wss:' : 'ws:'; + var wsUrl = proto + '//' + location.host + '/ws-upload'; + for (var i = 0; i < files.length; i++) { var f = files[i]; if (f.status === 'ok') continue; @@ -165,7 +168,7 @@ async function uploadAll() { renderTable(); try { - var result = await uploadFileHTTP(f, i); + var result = await uploadFileWS(wsUrl, f, i); f.status = 'ok'; f.elapsed = result.elapsed_ms; f.speed = result.elapsed_ms > 0 ? Math.round(result.bytes / (result.elapsed_ms / 1000)) : null; @@ -193,53 +196,75 @@ async function uploadAll() { fileSummary.textContent = summary; } -/** uploadFileHTTP(f, idx) — загрузить файл как raw body (без base64) */ -function uploadFileHTTP(f, idx) { +/** uploadFileWS(url, f, idx) — WebSocket с 8KB чанками и server ACK */ +function uploadFileWS(url, f, idx) { return new Promise(function (resolve, reject) { + var ws = new WebSocket(url); + var CHUNK = 8 * 1024; + var offset = 0; var startTime = Date.now(); - var RETRY = 3; + var lastProgress = 0; + var timer; - function doUpload(attempt) { - var xhr = new XMLHttpRequest(); - xhr.open('POST', '/upload'); - xhr.timeout = 300000; - xhr.setRequestHeader('X-File-Name', encodeURIComponent(f.name)); - xhr.setRequestHeader('X-File-Size', f.size); + ws.binaryType = 'arraybuffer'; - xhr.upload.onprogress = function (e) { - if (e.lengthComputable) { - f.progress = Math.round(e.loaded / e.total * 100); - renderTable(); - } + ws.onopen = function () { + readNext(); + }; + + function readNext() { + var end = Math.min(offset + CHUNK, f.size); + var reader = new FileReader(); + reader.onload = function (e) { + ws.send(e.target.result); }; - - xhr.onload = function () { - try { - var r = JSON.parse(xhr.responseText); - resolve({ bytes: r.bytes, elapsed_ms: Date.now() - startTime }); - } catch (e) { - if (attempt < RETRY) { doUpload(attempt + 1); return; } - reject(new Error('Bad response')); - } - }; - - xhr.onerror = function () { - if (attempt < RETRY) { doUpload(attempt + 1); return; } - reject(new Error('Network error')); - }; - - xhr.ontimeout = function () { - if (attempt < RETRY) { doUpload(attempt + 1); return; } - reject(new Error('Timeout')); - }; - - xhr.send(f.file); + reader.readAsArrayBuffer(f.file.slice(offset, end)); } - doUpload(1); + ws.onmessage = function (e) { + try { + var r = JSON.parse(e.data); + if (r.ack) { + offset = r.ack; + var pct = Math.round(offset / f.size * 100); + if (pct - lastProgress >= 10) { + lastProgress = pct; + f.progress = pct; + renderTable(); + } + if (offset < f.size) { + readNext(); + } else { + ws.send('DONE'); + } + } else if (r.ok) { + clearTimeout(timer); + f.progress = 100; + renderTable(); + ws.close(); + resolve({ bytes: r.bytes, elapsed_ms: Date.now() - startTime }); + } + } catch (err) { + reject(new Error('Bad response')); + } + }; + + ws.onerror = function () { reject(new Error('WebSocket error')); }; + + ws.onclose = function () { + if (offset < f.size) reject(new Error('WS closed early')); + }; + + timer = setTimeout(function () { + ws.close(); + reject(new Error('Timeout')); + }, 300000); }); } +// HTTP upload больше не используется +function uploadFileHTTP() {} + // WebSocket upload больше не используется; оставлен для справки function uploadFileWS() {}