data modified after send with ajax - javascript

I am using javascript and the Flask framework
I would like to retrieve in Flask in python the bytes of one or more files that the user will have chosen in my HTML page. To do this, when the user has chosen the files and has clicked on the send button, it triggers a function that uses the FileReader API to retrieve the base64 content of the file(s) that the user has selected.
After that, I would like to send the base64 data to flask with ajax.
But here is my problem, when I get the base64 string in python and compare it with the one in javascript, I notice the number of characters is exactly the same, but some characters are different as you can see on the screenshots below
size of character strings
And when I decode the base64 string, I get different bytes :
bytes variables
This makes me think that the problem is with the use of ajax
Code Python :
#app.route('/test', methods=['POST'])
def test():
if request.method == "POST":
files = eval(request.form.get("files"))
python_data = file.get("data")
javascript_data = "" # Copy from chrome console
len_python_data = len(python_data)
len_javascript_data = len(javascript_data)
base64_bytes_javascript = base64.b64decode(javascript_data)
base64_bytes_python = base64.b64decode(python_data)
Code Javascript :
let array_files = [];
let files_input = document.getElementById("files_input");
let files = files_input.files;
let reader = new FileReader();
function readFile(index) {
if( index >= files.length ) {
let data = "files=" + JSON.stringify(array_files);
$.ajax({
url: '/test',
type: 'POST',
data: data,
success: function (msg) {
console.log(msg)
}
});
return;
}
let file = files[index];
reader.name = file.name;
reader.onload = function() {
let file_info = {};
// get file content
let bin = this.result;
console.log(bin)
let data = bin.split(";")[1].replace("base64,", "");
file_info.name = reader.name;
file_info.data = data;
array_files.push(file_info);
readFile(index + 1)
}
reader.readAsDataURL(file);
}
readFile(0);

The problem is solved
In the base64 character string, the "+" were replaced by spaces after sending it to ajax.

Related

Send a byte array to WCF service

I am trying to send a pdf file from javascript to a rest wcf service.
The service expects an array of byte with the following signature
The trick is in the byte array parameter, all the others are working fine
[OperationContract]
[WebInvoke(UriTemplate = "rest/{sessionToken}/ImportNewTemplate?commit={commit}&createApplication={createApplication}&templateName={templateName}&option={option}")]
[CloudMethod(Group = "02. Templates", Description = "Import a new template in the platform.", HelpFile = "ListPaperTemplate.aspx")]
[CloudParameter(Name = "sessionToken", Description = "session token", HelpFile = "ServiceAPIDoc.aspx?q=sessionToken")]
[CloudParameter(Name = "createApplication", Description = "Create a standalone application linked to this template.")]
[CloudParameter(Name = "commit", Description = "Commit the upload ? if true, the template will be imported, else the return just allow you to preview template description.")]
[CloudParameter(Name = "templateName", Description = "Name of the new template. Only valid for single pdf upload. If the files are zipped, the file name in the zip will be used instead")]
[CloudParameter(Name = "templateFile", Description = "Can be a PDF file, or a zip file containing a flat pdf + xml definition", HelpFile = "ServiceAPIDoc.aspx?q=templateFile")]
CloudObjects.TemplateImportation ImportNewTemplate(string sessionToken, bool commit, bool createApplication, byte[] templateFile, string templateName, string option);
this is what I use from the javascript end to send the pdf file
const file = e.target.files[0];
// Encode the file using the FileReader API
const reader = new FileReader();
var fileByteArray = [];
reader.onloadend = async (e) => {
const arrayBuffer = e.target.result,
array = new Uint8Array(arrayBuffer);
for (const a of array) {
console.log(a);
fileByteArray.push(a);
}
let ret = await dispatch('createTemplate', {name: this.newForm.name, pdf:fileByteArray, save:false});
await this.$store.dispatch('hideLoadingScreen')
// Logs data:<type>;base64,wL2dvYWwgbW9yZ...
};
reader.onerror = async () => {
await this.$store.dispatch('hideLoadingScreen')
}
reader.onabort = async () => {
await this.$store.dispatch('hideLoadingScreen')
}
await this.$store.dispatch('showLoadingScreen');
reader.readAsArrayBuffer(file);
And here is the code to send it to the rest service
let url = `${getters.getServiceUrl}ImportNewTemplate?templateName=${name}&commit=${save || true}`
const xhr = new XMLHttpRequest;
xhr.open("POST", url, false);
xhr.setRequestHeader('Content-Type', 'application/json');
let response = await xhr.send(pdf);
However every time I get an error from the service when it tries to deserialise the byte array.
The exception message is 'There was an error deserializing the object of type System.Byte[]. End element 'root' from namespace '' expected.
I have tried a lot of alternatives but nothing works.
Any suggestions are welcome !
Thanks
For those interested, the trick was to add JSON.stringify to the returned array.
So: xhr.send(JSON.stringify(pdf))
would do the trick

Convert input files to byte[] javascript

I'm working on a REST web application that manages documents between users and uploaders. The backend is written in Java and my Document entity contains, besides various attributes, a byte[] content. I was able to send a file created at server side by
#GET
...
document.setContent(Files.readAllBytes(Paths.get("WEB-INF/testFile.txt")));
return Response.ok(document).build();
and retrieve it at front-end (vueJs) through
async function download(file) {
const fileURL = window.URL.createObjectURL(new Blob([atob(file.content)]));
const fileLink = document.createElement("a");
fileLink.href = fileURL;
fileLink.setAttribute("download",`${file.name}.${file.type}`);
document.body.appendChild(fileLink);
fileLink.click();
fileLink.remove;
window.URL.revokeObjectURL(fileURL);
}
the problem is that when I try to upload a file and then download it, its content is not parsed correctly (is shown undefined, string in Base64 or numbers depending on how I try to solve it). The file is sent by a post request and is retrieved through an input form bound to an onFileSelected function.
function onFileSelected(e) {
var reader = new FileReader();
reader.readAsArrayBuffer(e.target.files[0]);
reader.onloadend = (evt) => {
if (evt.target.readyState === FileReader.DONE) {
var arrayBuffer = evt.target.result;
this.file.content = new Uint8Array(arrayBuffer);
//this.file.content = arrayBuffer;
}
};
}
axios.post(...,document,...)
and I have tried using atob and btoa as well before assigning the value to this.file.content. If I print the file on server Welcome.txt it gives B#ae3b74d and if I use Arrays.toString(welcome.getContent()) it gives an array of numbers but as soon as it passed to the frontend its content become in Base64 welcome: { ... content: IFRoaXMgaXMgYSB0ZXN0IGZpbGUhIAo...}. Any idea? Thank you a lot!

How to retrieve data as File object on flask webserver?

I am currently developing a web interface where you can draw a digit and upload it. The image goes through a machine learning model of digit recognition and return the digit that has been transcribed by the user.
HTML Code :
<canvas id="myCanvas">
Sorry, your browser does not support HTML5 canvas technology.
</canvas>
<button onclick="clickonbutton()">Upload</button>
Canvas is used to draw digit and button to send it to a flask web server.
Javascript Code :
<script type="text/javascript">
function clickonbutton() {
var canvas = document.getElementById('myCanvas');
//Convert HTML5 Canvas to bytes-like object then File object
canvas.toBlob(function(blob) {
var file = new File([blob], "image.png", { type: "image/png", lastModified : new Date()});
//Prepare a XMLHttpRequest to send the data with POST method to the server
var request = new XMLHttpRequest();
request.open("POST", "/");
request.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
//Sending file...
request.send(file);
});
}
</script>
When the user click on the button "Upload" after editing the HTML5 canvas, it run this function which send the file to the server.
Python script (server-side) :
#app.route('/', methods=['GET', 'POST'])
def upload_file():
if request.method == 'POST':
#Convert MultiDict to Dict object then string
data = str(request.form.to_dict())
#Format string to remove Dict elements
data = data.replace("{","")
data = data.replace("}","")
data = data.replace("'","")
data = data.replace(": ","")
#Convert string to bytes-like object
img_bytes = data.encode()
#Predict the digit of the user
_, predicted_class, probabilities = get_prediction(image_bytes=img_bytes)
predicted_class = int(predicted_class)
classify(img=_, ps=probabilities)
#Return the result of the digit prediction
return render_template('result.html',class_name=predicted_class)
return render_template('index.html')
On server-side, i get the data from request.form but these are in a ImmutableMultiDict object so i converted the data to string then bytes-like object. Unfortunately, conversion using the .replace() method corrupts the PNG file and there's no tool to do that conversion properly...
Is there a solution to get the data directly in a File object without using an ImmutableMultiDict object ?
So with help from this thread on using the Fetch API and this answer on HTML5 canvas drawing I figured this could be done with something like:
<div id='sketch'>
<canvas id="myCanvas">
</canvas>
</div>
<button id='btn'>Upload</button>
<div id='result' style='font-size:2em;'></div>
Notice I've added a div to output the results (the class name) on the same page.
Then in the Javascript use a FormData object: (I've missed out the drawing function for clarity):
// Define the dom elements
const canvas = document.getElementById('myCanvas');
const btn = document.getElementById('btn');
const result = document.getElementById('result');
// This will handle the upload
const upload = (file) => {
fetch('/', { // Your POST endpoint
method: 'POST',
body: file // This is your file object
}).then(
response => response.json() // if the response is a JSON object
).then(
success => { result.innerText = success['class_name']; } // Write class_name into results div.
).catch(
error => console.log(error) // Handle the error response object
);
};
// Listener which handles the button click:
btn.addEventListener('click', function() {
canvas.toBlob(function(blob) {
var data = new FormData();
data.append('file', blob);
upload(data);
});
}, false);
Now on the server side, this can be handled like a regular Flask file upload:
#app.route('/', methods=['GET', 'POST'])
def upload_file():
if request.method == 'POST':
file = request.files['file']
#Convert string to bytes-like object
img_bytes = file.read()
#Predict the digit of the user
_, predicted_class, probabilities = get_prediction(image_bytes=img_bytes)
predicted_class = int(predicted_class)
classify(img=_, ps=probabilities)
#Return the result of the digit prediction
return {'class_name': predicted_class}
return render_template('index.html')
Any post request now returns a jsonified dictionary, which is in turn written to the results div on the page.
There's probably room for improvment here, like some validation on the server-side aswell as rate limiting and error handling, but the basic concept works.

JavaScript/jQuery downloading PDF file returned from Django view

I have a celery task that creates PDF files on the click of a button.
When the button is clicked, the javascript side will keep checking until the task is done, and when it is, it will download the file. I figured some recursivity would do:
$(".getPdf").on('click', function(event) {
// Grab the clicked object ID and the filename stored on the data attributes
var thisId = $(this).data('runid');
var filename = $(this).data('filename');
var taskID = "None";
var pdfStatus = "None";
// Run download function
downloadPdf(filename, taskID, pdfStatus);
function downloadPdf(filename, taskID, pdfStatus) {
// Send a POST request to Django's RunsView with the Run ID, pdf filename, task ID and pdfStatus (if any)
$.post({
url: "{% url 'runs' %}",
data: {
csrfmiddlewaretoken: "{{ csrf_token }}",
id: thisId,
filename: filename,
task_id: taskID,
},
success: function(data) {
// Split the returned string to separate the task ID [0] from the status [1]
var taskID = data.split(";")[0];
var pdfStatus = data.split(";")[1];
// Convert the pdfStatus Python bools into JavaScript bools
if (pdfStatus == "False") {
pdfStatus = false;
} else if (pdfStatus == "True") {
pdfStatus = true;
};
if (!pdfStatus) {
console.log("Repeat function.");
downloadPdf(filename, taskID, pdfStatus);
} else {
console.log("Download PDF");
window.open("data:application/pdf;base64," + data);
};
},
traditional: true
}).done();
};
});
The actual JavaScript side of the PDF download is simple enough. I send the ID of the object I want to generate a PDF of (a "Run"), along with the filename that it should have (this is generated on the get part of the same page's Django view), and an empty celery task ID (the task has not been created yet of course). The response I get back is a string made of "celery task ID;" with False if the task is not done yet, so I repeat the POST request.
On the backend side of things, I handle the POST request depending on if the task ID is present or not:
def post(self, request):
# Get the args from the incoming request
run_id = request.POST.get('id')
filename = request.POST.get('filename')
task_id = request.POST.get('task_id')
if run_id and task_id == "None":
# Database and Django stuff happens here for a few lines...
# Fire off a Celery task to generate the PDF file asynchronously
pdf_task = create_pdf.delay(sensor, range_period)
response_string = str(pdf_task.task_id) + ";" + str(AsyncResult(pdf_task.task_id).ready())
return HttpResponse(response_string)
elif task_id != "None":
pdf_done = AsyncResult(task_id).ready() # If this is false, send the task ID back with a False
if not pdf_done:
response_string = str(task_id) + ";" + str(pdf_done)
return HttpResponse(response_string)
# Otherwise send the PDF back
else:
pdf_task = AsyncResult(task_id)
pdf_file = pdf_task.result
pdf_file_location = settings.MEDIA_ROOT + '/reports/' + pdf_file
# response = HttpResponse(content_type='application/pdf')
# response['Content-Disposition'] = 'attachment; filename={}'.format(pdf_task.result)
# # return response
try:
# with open(pdf_file_location, 'r') as pdf:
# response = HttpResponse(pdf.read(), content_type='application/pdf')
# response['Content-Disposition'] = 'attachment; filename="' + pdf_task.result + '"'
# pdf.close()
return FileResponse(open(pdf_file_location, 'rb'), content_type='application/pdf')
except IOError:
print("File does not exist, check above.")
return response
I've gone ahead and left in the attempts I made at making the file download work with the AJAX request. So far the one that's not commented out has actually returned a string of encoded text to the front end, but I'm not sure how to decode that, I end up with a tab that looks like this:
So the encoded PDF is reaching the front end after the file is done processing (that part is fine), but I'm not good enough at JavaScript to figure out how to turn the encoded string into a PDF file.
You can decode base64 to Blob and download it as file.
var blob = new Blob(atob(data), {type: 'application/pdf'})
var link = document.createElement("a");
link.href = window.URL.createObjectURL(blob);
link.download = "filename.pdf";
document.body.appendChild(link);
link.click()

Jquery ajax, send very big data base64, how optimize that

So, i have a script for capture a video from webcam and after create a gif with :
var base64data;
var img = document.createElement('img');
var reader = new window.FileReader();
reader.readAsDataURL(blob);
reader.onloadend = function() {
base64data = reader.result;
img.src = base64data;
}
So, with this script, i take the blob object from webcam, i encode this blob into base64 and i put the data into my img.src.
After that, i need to send this data into PHP with jquery ajax.
The base64data is very big, like 2 000 000 characters, so the ajax request is very long (20-50sec).
I just send the data like that :
var gif = $('.generated-img').attr('src').replace('data:image/gif;base64,', '');
gif = gif.match(/.{1,500000}/g);
$.ajax({
type:"POST",
url: "/webcam/",
data: {image_gif:gif, crop_x:x, crop_y:y, crop_w:w, crop_h:h, gif:true},
success: function () {
parent.$.fancybox.close();
}
});
I need to break the data into several chunks of 500 000 characters each.
But it's soooo long.. How can i do for optimize that ? I just need to retrieve this data into my php script for do some stuff...
I don't see exactly why you need to split it up; the idea of using AJAX - where A stands for asynchronous - is precisely that you can do big slow things in the background.
But rather than using a regular expression to do the split, I would just break it up using substr, e.g. something like (not tested, so may be offset errors - e.g. you may need a + 1 or - 1 here and there):
var chunkSize = 500000;
var chunkId = 0;
var isLastChunk = false;
for(var i = 0; i < gif.length; i += chunkSize)
{
var chunk;
if(gif.length > i + chunkSize) {
chunk = gif.substr(0, i, chunkSize);
} else {
chunk = gif.substr(0, i);
isLastChunk = true;
}
$.ajax({
type:"POST",
url: "/webcam/",
data: {
chunk: chunk, chunkId: chunkId, isLastChunk: isLastChunk,
crop_x:x, crop_y:y, crop_w:w, crop_h:h, gif:true},
success: function () {
// Chunk handled succesfully
}
});
}
In /webcam/ you could check chunkId, and isLastChunk.

Categories