提问人:Mr_BananaPants 提问时间:9/20/2023 更新时间:9/21/2023 访问量:28
将上传的文件从 JS 发送到 Python CGI 总是会产生 ampty 字典
Sending uploaded file from JS to Python CGI always results in ampty dictionary
问:
我有一个带有文件输入元素的 HTML 表单。提交表单时,会执行JS功能,检查上传的文件是否为PDF文件且小于10MB。所有这些都可以正常工作,并在控制台中记录正确的文件大小。
然后 JS 函数通过 HTTP POST 请求将文件发送到 Python 服务器。但是,发送的文件始终是空的对象/字典。当我在发送之前,所有文件数据都会被记录下来,但是当我在检查器中查看“网络”选项卡时,它只是一个空对象/字典:console.log(file);
但是,当我更改我的 HTML 表单时,它将数据直接发送到 Python CGI 脚本而无需先调用 JS 函数,它可以完美地工作。
这是我的 HTML 代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="./js/new.js"></script>
<title>test</title>
</head>
<body>
<script></script>
<header>
<h1>File upload test</h1>
</header>
<main>
<fieldset>
<form
action="javascript:addFile()"
id="DataForm"
>
<div>
<label for="file_contract">contract:</label
><input
type="file"
accept="application/pdf"
id="file_contract"
name="file_contract"
/>
</div>
<button type="button" class="outlined" onclick="cancel()">
Annuleer
</button>
<input type="submit" value="Bewaar" />
</form>
</fieldset>
</main>
<script>
function cancel() {
const host = window.location.host;
const redirectUrl = `http://${host}`;
window.location.replace(redirectUrl);
}
</script>
</body>
</html>
JavaScript 代码:
async function addFile() {
const formData = new FormData(document.forms["DataForm"]);
// get uploaded files
// Function to check and append a file to the formData
function handleFileUpload(fileInputId) {
console.log("uploading file...");
const fileInput = document.getElementById(fileInputId);
const file = fileInput.files[0];
// Check if a file is selected
// Ensure the file is a PDF
if (file.type === "application/pdf") {
console.log(`filesize = ${file.size}`);
// 10MB in bytes
formData.append(fileInputId, fileInput.files[0]);
}
return true;
}
// Handle each file upload separately
if (!handleFileUpload("file_contract")) {
console.log("form submission prevented");
return false; // Prevent form submission
}
const data = {
answers: Object.fromEntries(formData),
};
console.log(data["answers"]["file_contract"]);
try {
const response = await fetch("../../cgi-bin/addRecord.py", {
method: "POST",
headers: {
"Content-type": "application/json; charset=UTF-8",
},
body: JSON.stringify(data),
});
const responseData = await response.json();
if (responseData.hasOwnProperty("status")) {
if (responseData["status"] === "failed") {
// session is invalid / expired
alert("Login sessie vervallen. Log opnieuw in.");
} else if (responseData.status === "success") {
const host = window.location.host;
const redirectUrl = `http://${host}`;
console.log("redirecting... (commented out)");
// window.location.replace(redirectUrl);
} else {
alert(
"Fout bij het opslaan. Probeer het nog eens of contacteer Sandra."
);
}
}
} catch (error) {
console.error(error);
alert("Fout bij het opslaan. Probeer het nog eens of contacteer Sandra.");
}
}
Python CGI:
#!/usr/bin/env python3
import cgi
import hashlib
import json
import os
import sys
import logging
# Get the absolute path of the current script
dirname = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if os.path.exists(f'{dirname + "/log.log"}'):
# If it exists, open it in write mode to clear its contents
with open(f'{dirname + "/log.log"}', 'w'):
pass
logging.basicConfig(filename=f'{dirname + "/log.log"}', level=logging.DEBUG)
def generate_response(status, data=None):
response = {"status": status}
if data:
response["data"] = data
print("Content-Type: application/json")
print()
print(json.dumps(response))
def calculate_file_hash(file_data):
# Create an MD5 hash object
md5_hash = hashlib.md5()
# Read the file data in chunks to efficiently handle large files
for chunk in iter(lambda: file_data.read(4096), b''):
md5_hash.update(chunk)
# Return the hexadecimal representation of the MD5 hash
return md5_hash.hexdigest()
def main():
# read form data from HTTP POST request
data = sys.stdin.read(int(os.environ.get('CONTENT_LENGTH', 0)))
post_data = json.loads(data)
answers = post_data["answers"]
logging.debug(str(answers))
if "file_contract" in answers:
contract_file = answers['file_contract']
contract_file_filename = contract_file.filename
contract_file_hash = calculate_file_hash(contract_file.file)
# save the file data to the werkmap
# Save the PDF file to a folder
contract_filename_path = os.path.join(dirname, "documenten", contract_file_hash)
with open(contract_filename_path, 'wb') as contract_file_handle:
contract_file_handle.write(contract_file.file.read())
generate_response("success")
if __name__ == "__main__":
main()
答:
问题是.它无法将对象转换为字符串。JSON.stringify
File
JSON
像往常一样发送它会更简单(并向此表单添加其他元素)。FormData
如果您真的必须发送它,那么您可能需要使用和功能将文件内容转换为 .以 JSON 形式发送数据(即图像、数据文件)是流行的方法。JSON
FileReader
readAsDataURL()
Base64
const file = formData.get('file_contract');
const reader = new FileReader();
reader.onload = your_function_executed_after_converting;
reader.readAsDataURL(file);
最小的工作示例。我删除了测试此问题不需要的元素。
async function addFile() {
const formData = new FormData(document.forms["DataForm"]);
const file = formData.get('file_contract');
const reader = new FileReader();
reader.onload = () => {
//const data = {'answers': reader.result} // send only file contemt
const data = {'answers':
{
'data': reader.result, // file content as `Base64`
'name': file.name,
'size': file.size,
'type': file.type,
'lastModified': file.lastModified,
}
}
fetch("/cgi-bin/addRecord.py", {
method: "POST",
headers: {
//'Accept': 'application/json, text/plain, */*', // expect JSON or TEXT as response from server
'Content-Type': 'application/json; charset=UTF-8' // inform server that you send JSON
},
body: JSON.stringify(data),
}).then((response) => {
if(response.ok) {
const responseData = response.json();
//TODO: use responseData
}
});
}
reader.readAsDataURL(file); // convert to `Base64` and execute `onload()`
}
因为它以 (带前缀) 的形式发送数据,所以稍后在 Python 中您必须删除前缀并解码回正常状态。base64
application/pdf;base64,
application/pdf;base64,
base64
bytes
import base64
file_as_bytes = base64.b64decode(file_as_base64)
我使用框架而不是 .flask
CGI
这是我用来测试它的完整工作代码。
所有代码(HTML、JavaScript、Python)都在一个文件中。
import os
from flask import Flask, request, Response
import base64
app = Flask(__name__)
@app.route('/')
def index():
return '''<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="code.js"></script>
<title>test</title>
</head>
<body>
<form method="POST" enctype="multipart/form-data" action="javascript:addFile()" id="DataForm">
<div>
<label for="file_contract">contract:</label>
<input type="file" accept="application/pdf" id="file_contract" name="file_contract" />
</div>
<button type="button" onclick="cancel()">Cancel</button>
<input type="submit" value="Submit" />
</form>
</body>
</html>
'''
@app.route('/code.js')
def code():
text = '''async function addFile() {
const formData = new FormData(document.forms["DataForm"]);
const file = formData.get('file_contract');
const reader = new FileReader();
reader.onload = () => {
//const data = {'answers': reader.result} // send only file contemt
const data = {'answers':
{
'data': reader.result, // file content as `Base64`
'name': file.name,
'size': file.size,
'type': file.type,
'lastModified': file.lastModified,
}
}
fetch("/addRecord", {
method: "POST",
headers: {
//'Accept': 'application/json, text/plain, */*', // expect JSON or TEXT as response from server
'Content-Type': 'application/json; charset=UTF-8' // inform server that you send JSON
},
body: JSON.stringify(data),
}).then((response) => {
if(response.ok) {
const responseData = response.json();
//TODO: use responseData
}
});
}
reader.readAsDataURL(file); // convert to `Base64` and execute `onload()`
}
'''
return Response(text, mimetype='application/javascript')
@app.route('/addRecord', methods=['POST'])
def add_record():
print('args :', request.args)
print('form :', request.form)
if request.is_json:
print('json :', request.json)
print('files:', request.files)
print('data :', request.data[:100]) # crop data to 100 chars - to display only short example
# ----------------------------------------
file_info = request.json['answers']
data = file_info['data'] # file data with prefix `application/pdf;base64,`
# find beginning of data (after prefix `application/pdf;base64,`)
file_start = data.find(',') + 1 # +1 == len(',')
# crop data to remove prefix
file_data = data[file_start:]
file_as_bytes = base64.b64decode(file_data)
print(file_as_bytes[:100]) # crop data to 100 chars - to display only short example
# write data to file on disk (using "bytes mode")
with open(file_info['name'], 'wb') as f_out:
f_out.write(file_as_bytes)
#file_as_text = file_as_bytes.decode("utf-8")
#print(file_as_text[:100]) # crop data to 100 chars - to display only short example
return {'status': 'OK'} # it will send dictionary as JSON
if __name__ == '__main__':
#app.debug = True
#app.run(threaded=False, processes=3)
#app.run(use_reloader=False)
app.run()
评论
console.log( JSON.stringify(data) )
console.log( data )
JSON
JSON.stringify(formData)
{}
JSON.stringify( Objects.fromEntries(formData) )
File
Objects.fromEntries()
JSON
reader = FileReader()
base64
reader.readAsDataURL()
JSON