mirror of
https://github.com/9001/copyparty.git
synced 2026-06-22 22:12:52 -06:00
feat(上传): 添加文件上传进度显示和测试用例
为文件上传功能添加进度条、状态显示和重试机制 添加全面的上传功能测试用例,包括大文件和多文件上传测试
This commit is contained in:
parent
d570f04d26
commit
154e3a5c9f
|
|
@ -31,11 +31,21 @@
|
||||||
|
|
||||||
<div id="op_bup" class="opview opbox {% if not ls0 %}act{% endif %}">
|
<div id="op_bup" class="opview opbox {% if not ls0 %}act{% endif %}">
|
||||||
<div id="u2err"></div>
|
<div id="u2err"></div>
|
||||||
<form method="post" enctype="multipart/form-data" accept-charset="utf-8" action="{{ url_suf }}">
|
<form id="bup_form" method="post" enctype="multipart/form-data" accept-charset="utf-8" action="{{ url_suf }}">
|
||||||
<input type="hidden" name="act" value="bput" />
|
<input type="hidden" name="act" value="bput" />
|
||||||
<input type="file" name="f" multiple /><br />
|
<input type="file" id="bup_files" name="f" multiple /><br />
|
||||||
<input type="submit" value="start upload">
|
<input type="submit" id="bup_submit" value="start upload">
|
||||||
</form>
|
</form>
|
||||||
|
<div id="bup_progress" style="display:none; margin-top:10px;">
|
||||||
|
<div id="bup_status" style="margin-bottom:5px; font-weight:bold;"></div>
|
||||||
|
<div id="bup_fileinfo" style="margin-bottom:5px; font-size:0.9em;"></div>
|
||||||
|
<div style="width:100%; background:#333; height:20px; border-radius:10px; overflow:hidden;">
|
||||||
|
<div id="bup_bar" style="width:0%; height:100%; background:linear-gradient(90deg, #09d, #4b0); transition:width 0.1s;"></div>
|
||||||
|
</div>
|
||||||
|
<div id="bup_percent" style="text-align:center; margin-top:5px; font-size:0.9em;"></div>
|
||||||
|
<div id="bup_speed" style="text-align:center; margin-top:5px; font-size:0.9em; color:#888;"></div>
|
||||||
|
</div>
|
||||||
|
<div id="bup_result" style="display:none; margin-top:10px; padding:10px; border-radius:5px;"></div>
|
||||||
<a id="bbsw" href="?b=u" rel="nofollow"><br />switch to basic browser</a>
|
<a id="bbsw" href="?b=u" rel="nofollow"><br />switch to basic browser</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -163,6 +173,237 @@
|
||||||
jsldp("J_BRW","browser");
|
jsldp("J_BRW","browser");
|
||||||
jsldp("J_U2K","up2k");
|
jsldp("J_U2K","up2k");
|
||||||
</script>
|
</script>
|
||||||
|
<script>
|
||||||
|
(function() {
|
||||||
|
var MAX_RETRIES = 3,
|
||||||
|
RETRY_DELAY = 1000,
|
||||||
|
progressDiv = null,
|
||||||
|
progressBar = null,
|
||||||
|
progressPercent = null,
|
||||||
|
progressSpeed = null,
|
||||||
|
progressStatus = null,
|
||||||
|
progressFileinfo = null,
|
||||||
|
resultDiv = null,
|
||||||
|
form = null,
|
||||||
|
submitBtn = null,
|
||||||
|
fileInput = null,
|
||||||
|
uploadStartTime = 0,
|
||||||
|
currentRetry = 0,
|
||||||
|
currentFiles = [],
|
||||||
|
currentFileIndex = 0,
|
||||||
|
totalFilesSize = 0;
|
||||||
|
|
||||||
|
function formatSize(bytes) {
|
||||||
|
if (bytes === 0) return '0 B';
|
||||||
|
var k = 1024,
|
||||||
|
sizes = ['B', 'KB', 'MB', 'GB', 'TB'],
|
||||||
|
i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||||
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatSpeed(bytesPerSec) {
|
||||||
|
if (bytesPerSec === 0) return '0 B/s';
|
||||||
|
var k = 1024,
|
||||||
|
sizes = ['B/s', 'KB/s', 'MB/s', 'GB/s'],
|
||||||
|
i = Math.floor(Math.log(bytesPerSec) / Math.log(k));
|
||||||
|
return parseFloat((bytesPerSec / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
function showProgress(show) {
|
||||||
|
if (progressDiv) {
|
||||||
|
progressDiv.style.display = show ? 'block' : 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showResult(success, message) {
|
||||||
|
if (resultDiv) {
|
||||||
|
resultDiv.style.display = 'block';
|
||||||
|
resultDiv.style.background = success ? '#050' : '#500';
|
||||||
|
resultDiv.style.color = '#fff';
|
||||||
|
resultDiv.innerHTML = message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideResult() {
|
||||||
|
if (resultDiv) {
|
||||||
|
resultDiv.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateProgress(loaded, total) {
|
||||||
|
if (!progressBar || !progressPercent) return;
|
||||||
|
|
||||||
|
var percent = total > 0 ? (loaded / total * 100) : 0;
|
||||||
|
progressBar.style.width = percent + '%';
|
||||||
|
progressPercent.textContent = percent.toFixed(1) + '%';
|
||||||
|
|
||||||
|
if (uploadStartTime > 0 && loaded > 0) {
|
||||||
|
var elapsed = (Date.now() - uploadStartTime) / 1000;
|
||||||
|
var speed = loaded / elapsed;
|
||||||
|
if (progressSpeed) {
|
||||||
|
progressSpeed.textContent = formatSpeed(speed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setStatus(text) {
|
||||||
|
if (progressStatus) {
|
||||||
|
progressStatus.textContent = text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setFileinfo(text) {
|
||||||
|
if (progressFileinfo) {
|
||||||
|
progressFileinfo.textContent = text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setFormEnabled(enabled) {
|
||||||
|
if (submitBtn) {
|
||||||
|
submitBtn.disabled = !enabled;
|
||||||
|
}
|
||||||
|
if (fileInput) {
|
||||||
|
fileInput.disabled = !enabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function uploadFile(file, formAction, callback) {
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
var formData = new FormData();
|
||||||
|
|
||||||
|
formData.append('act', 'bput');
|
||||||
|
formData.append('f', file);
|
||||||
|
|
||||||
|
uploadStartTime = Date.now();
|
||||||
|
currentRetry = 0;
|
||||||
|
|
||||||
|
setStatus('Uploading: ' + file.name);
|
||||||
|
setFileinfo('Size: ' + formatSize(file.size) + ' | Retry: ' + currentRetry + '/' + MAX_RETRIES);
|
||||||
|
updateProgress(0, file.size);
|
||||||
|
|
||||||
|
function doUpload() {
|
||||||
|
xhr.open('POST', formAction, true);
|
||||||
|
|
||||||
|
xhr.upload.onprogress = function(e) {
|
||||||
|
if (e.lengthComputable) {
|
||||||
|
updateProgress(e.loaded, e.total);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
xhr.onload = function() {
|
||||||
|
if (xhr.status === 200) {
|
||||||
|
var responseText = xhr.responseText || '';
|
||||||
|
if (responseText.indexOf('error') === -1 && responseText.indexOf('ERR') === -1) {
|
||||||
|
callback(null, xhr);
|
||||||
|
} else {
|
||||||
|
callback(new Error('Server error: ' + responseText), xhr);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
callback(new Error('HTTP error: ' + xhr.status), xhr);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
xhr.onerror = function() {
|
||||||
|
if (currentRetry < MAX_RETRIES) {
|
||||||
|
currentRetry++;
|
||||||
|
setFileinfo('Size: ' + formatSize(file.size) + ' | Retry: ' + currentRetry + '/' + MAX_RETRIES + ' - retrying...');
|
||||||
|
setTimeout(doUpload, RETRY_DELAY * currentRetry);
|
||||||
|
} else {
|
||||||
|
callback(new Error('Network error after ' + MAX_RETRIES + ' retries'), xhr);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
xhr.ontimeout = function() {
|
||||||
|
if (currentRetry < MAX_RETRIES) {
|
||||||
|
currentRetry++;
|
||||||
|
setFileinfo('Size: ' + formatSize(file.size) + ' | Retry: ' + currentRetry + '/' + MAX_RETRIES + ' - timeout, retrying...');
|
||||||
|
setTimeout(doUpload, RETRY_DELAY * currentRetry);
|
||||||
|
} else {
|
||||||
|
callback(new Error('Timeout after ' + MAX_RETRIES + ' retries'), xhr);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
xhr.timeout = 300000;
|
||||||
|
xhr.send(formData);
|
||||||
|
}
|
||||||
|
|
||||||
|
doUpload();
|
||||||
|
}
|
||||||
|
|
||||||
|
function uploadNextFile(formAction) {
|
||||||
|
if (currentFileIndex >= currentFiles.length) {
|
||||||
|
showProgress(false);
|
||||||
|
showResult(true, 'All files uploaded successfully! (' + currentFiles.length + ' file(s), ' + formatSize(totalFilesSize) + ')');
|
||||||
|
setFormEnabled(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var file = currentFiles[currentFileIndex];
|
||||||
|
|
||||||
|
uploadFile(file, formAction, function(err, xhr) {
|
||||||
|
if (err) {
|
||||||
|
showProgress(false);
|
||||||
|
showResult(false, 'Upload failed for "' + file.name + '": ' + err.message);
|
||||||
|
setFormEnabled(true);
|
||||||
|
} else {
|
||||||
|
currentFileIndex++;
|
||||||
|
uploadNextFile(formAction);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function initBupUpload() {
|
||||||
|
form = document.getElementById('bup_form');
|
||||||
|
submitBtn = document.getElementById('bup_submit');
|
||||||
|
fileInput = document.getElementById('bup_files');
|
||||||
|
progressDiv = document.getElementById('bup_progress');
|
||||||
|
progressBar = document.getElementById('bup_bar');
|
||||||
|
progressPercent = document.getElementById('bup_percent');
|
||||||
|
progressSpeed = document.getElementById('bup_speed');
|
||||||
|
progressStatus = document.getElementById('bup_status');
|
||||||
|
progressFileinfo = document.getElementById('bup_fileinfo');
|
||||||
|
resultDiv = document.getElementById('bup_result');
|
||||||
|
|
||||||
|
if (!form || !submitBtn || !fileInput) {
|
||||||
|
console.log('Basic upload form not found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
form.addEventListener('submit', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (!fileInput.files || fileInput.files.length === 0) {
|
||||||
|
showResult(false, 'Please select at least one file to upload.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
hideResult();
|
||||||
|
showProgress(true);
|
||||||
|
setFormEnabled(false);
|
||||||
|
|
||||||
|
currentFiles = Array.from(fileInput.files);
|
||||||
|
currentFileIndex = 0;
|
||||||
|
totalFilesSize = 0;
|
||||||
|
|
||||||
|
for (var i = 0; i < currentFiles.length; i++) {
|
||||||
|
totalFilesSize += currentFiles[i].size;
|
||||||
|
}
|
||||||
|
|
||||||
|
setStatus('Preparing upload...');
|
||||||
|
setFileinfo('Total: ' + currentFiles.length + ' file(s), ' + formatSize(totalFilesSize));
|
||||||
|
|
||||||
|
var formAction = form.action || window.location.href;
|
||||||
|
uploadNextFile(formAction);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', initBupUpload);
|
||||||
|
} else {
|
||||||
|
initBupUpload();
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|
|
||||||
276
tests/test_upload.py
Normal file
276
tests/test_upload.py
Normal file
|
|
@ -0,0 +1,276 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# coding: utf-8
|
||||||
|
from __future__ import print_function, unicode_literals
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import tempfile
|
||||||
|
import time
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from copyparty.authsrv import AuthSrv
|
||||||
|
from copyparty.httpcli import HttpCli
|
||||||
|
from tests import util as tu
|
||||||
|
from tests.util import Cfg
|
||||||
|
|
||||||
|
|
||||||
|
def hdr(query):
|
||||||
|
h = "GET /{} HTTP/1.1\r\nCookie: cppwd=o\r\nConnection: close\r\n\r\n"
|
||||||
|
return h.format(query).encode("utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def build_multipart_form(files, boundary="XD"):
|
||||||
|
"""
|
||||||
|
Build a multipart/form-data request body for testing uploads.
|
||||||
|
"""
|
||||||
|
parts = []
|
||||||
|
|
||||||
|
parts.append("--{}\r\n".format(boundary).encode("utf-8"))
|
||||||
|
parts.append('Content-Disposition: form-data; name="act"\r\n\r\n'.encode("utf-8"))
|
||||||
|
parts.append("bput\r\n".encode("utf-8"))
|
||||||
|
|
||||||
|
for filename, content in files:
|
||||||
|
parts.append("--{}\r\n".format(boundary).encode("utf-8"))
|
||||||
|
parts.append(
|
||||||
|
'Content-Disposition: form-data; name="f"; filename="{}"\r\n\r\n'.format(filename).encode("utf-8")
|
||||||
|
)
|
||||||
|
if isinstance(content, str):
|
||||||
|
parts.append(content.encode("utf-8"))
|
||||||
|
else:
|
||||||
|
parts.append(content)
|
||||||
|
parts.append("\r\n".encode("utf-8"))
|
||||||
|
|
||||||
|
parts.append("--{}--\r\n".format(boundary).encode("utf-8"))
|
||||||
|
|
||||||
|
return b"".join(parts)
|
||||||
|
|
||||||
|
|
||||||
|
class TestUpload(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.td = tu.get_ramdisk()
|
||||||
|
self.maxDiff = 99999
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
os.chdir(tempfile.gettempdir())
|
||||||
|
shutil.rmtree(self.td)
|
||||||
|
|
||||||
|
def test_basic_upload(self):
|
||||||
|
"""Test basic upload functionality (bput)"""
|
||||||
|
td = os.path.join(self.td, "vfs")
|
||||||
|
os.mkdir(td)
|
||||||
|
os.chdir(td)
|
||||||
|
|
||||||
|
vcfg = ["up/::a"]
|
||||||
|
os.makedirs("up")
|
||||||
|
|
||||||
|
self.args = Cfg(v=vcfg, a=["o:o", "x:x"])
|
||||||
|
self.asrv = AuthSrv(self.args, self.log)
|
||||||
|
self.conn = tu.VHttpConn(self.args, self.asrv, self.log, b"")
|
||||||
|
|
||||||
|
files = [("test.txt", "Hello, World!")]
|
||||||
|
h, body = self.bup("up", files)
|
||||||
|
|
||||||
|
self.assertIn("HTTP/1.1 201", h)
|
||||||
|
|
||||||
|
self.assertTrue(os.path.exists("up/test.txt"))
|
||||||
|
with open("up/test.txt", "r") as f:
|
||||||
|
self.assertEqual(f.read(), "Hello, World!")
|
||||||
|
|
||||||
|
self.conn.shutdown()
|
||||||
|
|
||||||
|
def test_basic_upload_json_response(self):
|
||||||
|
"""Test basic upload with JSON response"""
|
||||||
|
td = os.path.join(self.td, "vfs")
|
||||||
|
os.mkdir(td)
|
||||||
|
os.chdir(td)
|
||||||
|
|
||||||
|
vcfg = ["up/::a"]
|
||||||
|
os.makedirs("up")
|
||||||
|
|
||||||
|
self.args = Cfg(v=vcfg, a=["o:o", "x:x"])
|
||||||
|
self.asrv = AuthSrv(self.args, self.log)
|
||||||
|
self.conn = tu.VHttpConn(self.args, self.asrv, self.log, b"")
|
||||||
|
|
||||||
|
files = [("test.txt", "Hello, JSON!")]
|
||||||
|
h, body = self.bup("up?j", files)
|
||||||
|
|
||||||
|
self.assertIn("HTTP/1.1 201", h)
|
||||||
|
self.assertIn("application/json", h)
|
||||||
|
|
||||||
|
response = json.loads(body)
|
||||||
|
self.assertEqual(response["status"], "OK")
|
||||||
|
self.assertEqual(response["sz"], len("Hello, JSON!"))
|
||||||
|
self.assertEqual(len(response["files"]), 1)
|
||||||
|
self.assertEqual(response["files"][0]["fn_orig"], "test.txt")
|
||||||
|
|
||||||
|
self.conn.shutdown()
|
||||||
|
|
||||||
|
def test_large_file_upload(self):
|
||||||
|
"""Test uploading a relatively large file (1MB)"""
|
||||||
|
td = os.path.join(self.td, "vfs")
|
||||||
|
os.mkdir(td)
|
||||||
|
os.chdir(td)
|
||||||
|
|
||||||
|
vcfg = ["up/::a"]
|
||||||
|
os.makedirs("up")
|
||||||
|
|
||||||
|
self.args = Cfg(v=vcfg, a=["o:o", "x:x"])
|
||||||
|
self.asrv = AuthSrv(self.args, self.log)
|
||||||
|
self.conn = tu.VHttpConn(self.args, self.asrv, self.log, b"")
|
||||||
|
|
||||||
|
large_content = b"A" * 1024 * 1024
|
||||||
|
files = [("large.bin", large_content)]
|
||||||
|
h, body = self.bup("up", files)
|
||||||
|
|
||||||
|
self.assertIn("HTTP/1.1 201", h)
|
||||||
|
|
||||||
|
self.assertTrue(os.path.exists("up/large.bin"))
|
||||||
|
with open("up/large.bin", "rb") as f:
|
||||||
|
self.assertEqual(f.read(), large_content)
|
||||||
|
|
||||||
|
self.conn.shutdown()
|
||||||
|
|
||||||
|
def test_multiple_files_upload(self):
|
||||||
|
"""Test uploading multiple files in one request"""
|
||||||
|
td = os.path.join(self.td, "vfs")
|
||||||
|
os.mkdir(td)
|
||||||
|
os.chdir(td)
|
||||||
|
|
||||||
|
vcfg = ["up/::a"]
|
||||||
|
os.makedirs("up")
|
||||||
|
|
||||||
|
self.args = Cfg(v=vcfg, a=["o:o", "x:x"])
|
||||||
|
self.asrv = AuthSrv(self.args, self.log)
|
||||||
|
self.conn = tu.VHttpConn(self.args, self.asrv, self.log, b"")
|
||||||
|
|
||||||
|
files = [
|
||||||
|
("file1.txt", "Content 1"),
|
||||||
|
("file2.txt", "Content 2"),
|
||||||
|
("file3.txt", "Content 3"),
|
||||||
|
]
|
||||||
|
h, body = self.bup("up?j", files)
|
||||||
|
|
||||||
|
self.assertIn("HTTP/1.1 201", h)
|
||||||
|
|
||||||
|
response = json.loads(body)
|
||||||
|
self.assertEqual(response["status"], "OK")
|
||||||
|
self.assertEqual(len(response["files"]), 3)
|
||||||
|
|
||||||
|
filenames = [f["fn_orig"] for f in response["files"]]
|
||||||
|
self.assertIn("file1.txt", filenames)
|
||||||
|
self.assertIn("file2.txt", filenames)
|
||||||
|
self.assertIn("file3.txt", filenames)
|
||||||
|
|
||||||
|
self.conn.shutdown()
|
||||||
|
|
||||||
|
def test_upload_without_write_permission(self):
|
||||||
|
"""Test upload without write permission - should fail"""
|
||||||
|
td = os.path.join(self.td, "vfs")
|
||||||
|
os.mkdir(td)
|
||||||
|
os.chdir(td)
|
||||||
|
|
||||||
|
vcfg = ["ro/::r"]
|
||||||
|
os.makedirs("ro")
|
||||||
|
|
||||||
|
self.args = Cfg(v=vcfg, a=["o:o", "x:x"])
|
||||||
|
self.asrv = AuthSrv(self.args, self.log)
|
||||||
|
self.conn = tu.VHttpConn(self.args, self.asrv, self.log, b"")
|
||||||
|
|
||||||
|
files = [("test.txt", "This should fail")]
|
||||||
|
h, body = self.bup("ro", files)
|
||||||
|
|
||||||
|
self.assertIn("HTTP/1.1 403", h)
|
||||||
|
|
||||||
|
self.assertFalse(os.path.exists("ro/test.txt"))
|
||||||
|
|
||||||
|
self.conn.shutdown()
|
||||||
|
|
||||||
|
def test_upload_file_size_limit(self):
|
||||||
|
"""Test upload exceeding file size limit"""
|
||||||
|
td = os.path.join(self.td, "vfs")
|
||||||
|
os.mkdir(td)
|
||||||
|
os.chdir(td)
|
||||||
|
|
||||||
|
vcfg = ["up/::a"]
|
||||||
|
os.makedirs("up")
|
||||||
|
|
||||||
|
self.args = Cfg(v=vcfg, a=["o:o", "x:x"], smax=100)
|
||||||
|
self.asrv = AuthSrv(self.args, self.log)
|
||||||
|
self.conn = tu.VHttpConn(self.args, self.asrv, self.log, b"")
|
||||||
|
|
||||||
|
large_content = b"A" * 200
|
||||||
|
files = [("too_large.txt", large_content)]
|
||||||
|
h, body = self.bup("up", files)
|
||||||
|
|
||||||
|
self.assertIn("HTTP/1.1 400", h) or self.assertIn("ERROR", body)
|
||||||
|
|
||||||
|
self.assertFalse(os.path.exists("up/too_large.txt"))
|
||||||
|
|
||||||
|
self.conn.shutdown()
|
||||||
|
|
||||||
|
def test_upload_special_characters_filename(self):
|
||||||
|
"""Test upload with special characters in filename"""
|
||||||
|
td = os.path.join(self.td, "vfs")
|
||||||
|
os.mkdir(td)
|
||||||
|
os.chdir(td)
|
||||||
|
|
||||||
|
vcfg = ["up/::a"]
|
||||||
|
os.makedirs("up")
|
||||||
|
|
||||||
|
self.args = Cfg(v=vcfg, a=["o:o", "x:x"])
|
||||||
|
self.asrv = AuthSrv(self.args, self.log)
|
||||||
|
self.conn = tu.VHttpConn(self.args, self.asrv, self.log, b"")
|
||||||
|
|
||||||
|
files = [("my file 123.txt", "Content with spaces")]
|
||||||
|
h, body = self.bup("up?j", files)
|
||||||
|
|
||||||
|
self.assertIn("HTTP/1.1 201", h)
|
||||||
|
|
||||||
|
response = json.loads(body)
|
||||||
|
self.assertEqual(response["status"], "OK")
|
||||||
|
|
||||||
|
self.conn.shutdown()
|
||||||
|
|
||||||
|
def bup(self, url, files):
|
||||||
|
"""
|
||||||
|
Perform a basic upload (bput) request.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
url: The URL path to upload to
|
||||||
|
files: List of (filename, content) tuples
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
(headers, body) tuple
|
||||||
|
"""
|
||||||
|
boundary = "XD"
|
||||||
|
body = build_multipart_form(files, boundary)
|
||||||
|
|
||||||
|
hdr_fmt = "POST /{} HTTP/1.1\r\nPW: o\r\nConnection: close\r\nContent-Type: multipart/form-data; boundary={}\r\nContent-Length: {}\r\n\r\n"
|
||||||
|
hdr_str = hdr_fmt.format(url, boundary, len(body))
|
||||||
|
|
||||||
|
full_request = hdr_str.encode("utf-8") + body
|
||||||
|
print("POST -->", full_request[:200], "...")
|
||||||
|
|
||||||
|
conn = self.conn.setbuf(full_request)
|
||||||
|
HttpCli(conn).run()
|
||||||
|
|
||||||
|
response = conn.s._reply
|
||||||
|
if isinstance(response, bytes):
|
||||||
|
parts = response.split(b"\r\n\r\n", 1)
|
||||||
|
h = parts[0].decode("utf-8")
|
||||||
|
b = parts[1].decode("utf-8") if len(parts) > 1 else ""
|
||||||
|
else:
|
||||||
|
parts = response.split("\r\n\r\n", 1)
|
||||||
|
h = parts[0]
|
||||||
|
b = parts[1] if len(parts) > 1 else ""
|
||||||
|
|
||||||
|
print("POST <--", h[:200], "...")
|
||||||
|
return h, b
|
||||||
|
|
||||||
|
def log(self, src, msg, c=0):
|
||||||
|
print(msg)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Loading…
Reference in a new issue