Pass PNG on form submit, Request URL Too long - javascript

So I have an interesting question. I have a form where a user draws an image on a canvas (think a signature pad). I then need to send the image to my C# Controller (I am using ASP.NET MVC 5). The code I have functions for shorter strings, but when I try to pass the PNG data, it is too long and I recieve a HTTP Error 414. The request URL is too long error. Here is my code:
Html:
<form id="mainForm" action="submitUserAnswer" method="post">
<input type="hidden" id="userOutput" name="output" value="" />
//...other form elements, signature box, etc.
</form>
Javascript:
function goToNextQuestion() {
var output = $('#signature').jSignature("getData");
$('#userOutput').val(output);
$('#mainForm').submit();
}
C#:
public ActionResult submitUserAnswer()
{
//use the userOutput for whatever
//submit string to the database, do trigger stuff, whatever
//go to next question
System.Collections.Specialized.NameValueCollection nvc = Request.Form;
string userOutput = nvc["output"];
ViewBag.Question = userOutput;
return RedirectToAction("redirectToIndex", new { input = userOutput });
}
public ActionResult redirectToIndex(String input)
{
ViewBag.Answer = input;
return View("Index");
}
My png data is very long, so the error makes sense. My question is how can I get the png data back to my controller?

Maybe you just need to increase allowed GET request URL length.
If that doesn't works I have an aspx WebForm that saves a signature, and I use a WebMethod
[ScriptMethod, WebMethod]
public static string saveSignature(string data)
{
/*..Code..*/
}
and I call it like this:
PageMethods.saveSignature(document.getElementById('canvas').toDataURL(), onSucess, onError);
also I have to increase the length of the JSON request and it works fine, with no problems with the lenght.
In MVC there isn't WebMethods, but JSON and AJAX requests do the job, just save the data in a session variable, and then use it when need it.
Hope it helps

You have error because your data is string (base64) and have max limit for send characters, better way is to create blob (png file) from base64 at client side, and send it to server. Edit. All listed code here, exists in stackoverflow posts.
function dataURItoBlob(dataURI) {
// convert base64 to raw binary data held in a string
// doesn't handle URLEncoded DataURIs - see SO answer #6850276 for code that does this
var byteString = atob(dataURI.split(',')[1]);
// separate out the mime component
var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]
// write the bytes of the string to an ArrayBuffer
var ab = new ArrayBuffer(byteString.length);
var ia = new Uint8Array(ab);
for (var i = 0; i < byteString.length; i++) {
ia[i] = byteString.charCodeAt(i);
}
var blob = null;
// TypeError old chrome and FF
window.BlobBuilder = window.BlobBuilder ||
window.WebKitBlobBuilder ||
window.MozBlobBuilder ||
window.MSBlobBuilder;
if(window.BlobBuilder){
var bb = new BlobBuilder();
bb.append(ab);
blob = bb.getBlob(mimeString);
}else{
blob = new Blob([ab], {type : mimeString});
}
return blob;
}
function sendFileToServer(file, url, onFileSendComplete){
var formData = new FormData()
formData.append("file",file);
var xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
xhr.onload = onFileSendComplete;
xhr.send(formData);
}
var base64 = $('#signature').jSignature("getData");
var blob = dataURItoBlob(base64);
var onComplete = function(){alert("file loaded to server");}
sendFileToServer(blob, "/server", onComplete)

Related

Converting <input type=file> contents to Base64 and sending to Spring method expecting MultiPartFile

Because of server issues I need to convert the contents of a file upload to Base64 before it gets submitted to the server.
I've managed the JS side of things using reader.readAsDataURL to get the contents into Base64 in a local JS variable. I've then tried creating a new FormData and setting the base64 variable there - it replaces the name of the but then it also replaces the type - it's no longer a but just binary data - so when I send this to the server - I'm getting Spring error typeMismatch.org.springframework.web.multipart.MultipartFile
Basically - any thoughts how to convert file contents to Base64 (done that ok) but send to existing JAVA method with Spring MultiPartFile?
i.e. without me rewriting and adding extra fields in the FormData for file name and size etc (the stuff I'd get using the MultipartFile on the server end).
JS: (error handling removed)
var input = $(".actualFileInput");
var files = null;
// File data
if (input[0].files.length > 0) {
files = input[0].files;
}
var file = files[0], reader = new FileReader();
reader.onloadend = function () {
var b64 = reader.result.replace(/^data:.+;base64,/, '');
var name = input.attr('name');
input.attr('name', '');
var newFormData = new FormData(form); // New form with all data from the existing one
newFormData.set('uploadFile',b64); // replace with the base64 value of the selected file
var request = new XMLHttpRequest();
request.onreadystatechange = function () {
request.open(form.method, form.action, true);
request.onload = function() {
var url = window.location;
input.attr('name', name);
request.send(newFormData);
};
reader.readAsDataURL(file);
The Java at the server end:
#RequestMapping(method = RequestMethod.POST, params = "upload")
public String upload(#ModelAttribute("uploadDocument") UploadDocument document, BindingResult result,
ModelMap model, HttpServletRequest request, SessionStatus status) throws Exception {
UploadDocument is:
public class UploadDocument implements Serializable {
private static final long serialVersionUID = -3534003705995293025L;
// File upload work area
private MultipartFile uploadFile = null;
private String fileComment = null;
private Integer fileTypeId = null;
... (other fields in the <form>)
All the JAVA stuff works fine if I just submit the form. But in JS reading the file contents as Base64 then sending as a field doesnt get translated to MultiPartFile. Change to byte[] and add new fields for the file metadata myself? Or is there some trick I'm missing.
Thanks folks.
The way to make this JS variable send to a Spring MultiPartFile is:
newFormData.set('uploadFile',new Blob([b64]),files[0].name); // replace with the base64 value of the selected file
i.e. make it a blob, and 3rd arg is the file name the user selected in . This now sends the base64 value of the file contents.

Angular 2 Image resize before upload

Im looking to resize an image before it is uploaded to a server, at the moment i am using ng2-imageupload like this:
<input id="media" class="inputfile" type="file" name="media" image-upload
(imageSelected)="selected($event)"
[resizeOptions]="resizeOptions" (change)="onChange($event)">
export class WpMediaFormComponent {
file: File;
resizeOptions: ResizeOptions = {
resizeMaxHeight: 768,
resizeMaxWidth: 438
};
selected(imageResult: ImageResult) {
console.log(imageResult);
this.dataBlob = this.dataURItoBlob(imageResult.resized.dataURL);
let blob = this.dataURItoBlob(imageResult.resized.dataURL);
}
This then returns an object, like this:
dataURL:"data:image/jpeg;base64, DATA URI HERE"
type:"image/jpeg;"
I can then convert this object to a blob using this function:
dataURItoBlob(dataURI) {
// convert base64/URLEncoded data component to raw binary data held in a string
var byteString;
if (dataURI.split(',')[0].indexOf('base64') >= 0)
byteString = atob(dataURI.split(',')[1]);
else
byteString = decodeURI(dataURI.split(',')[1]);
// separate out the mime component
var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
// write the bytes of the string to a typed array
var ia = new Uint8Array(byteString.length);
for (var i = 0; i < byteString.length; i++) {
ia[i] = byteString.charCodeAt(i);
}
return new Blob([ia], {type:mimeString});
}
Before doing this I was uploading the image to the server using this code:
onChange(event: EventTarget) {
let eventObj: MSInputMethodContext = <MSInputMethodContext> event;
let target: HTMLInputElement = <HTMLInputElement> eventObj.target;
let files: FileList = target.files;
this.file = files[0];
console.log(this.file);
//this.update.emit(this.file);
}
Does anyone have idea how I can feed the blob returned from dataURItoBlob method into the file upload onChange event?
Im a little lost here.
So I figured it out with the help of #Brother Woodrow, and this thread:
How to convert Blob to File in JavaScript
Here is my updated code, not the only thing I had to change was the selected method:
selected(imageResult: ImageResult) {
// create a blob
let blob: Blob = this.dataURItoBlob(imageResult.resized.dataURL);
// get the filename
let fileName: string = imageResult.file.name;
// create a file
this.file = new File([blob], fileName);
console.log(this.file);
// event emitter send to container then to http post
this.update.emit(this.file);
}
I can now upload 3MB and they are pushed to the server around 150kB in seconds which is great for the user especially as this app will mostly be used by mobile devices.
You'll need to convert the Data URI to a Blob, then send that back to your server. This might be helpful: Convert Data URI to File then append to FormData
Once you have the blob, it should be easy enough to use FormData and the Angular HTTP class to upload it to your server for further processing.
let formData = new FormData();
formData.append(blob);
this.http.post('your/api/url', fd).subscribe((response) => console.log(reponse);

Retrieving audio file using Spring and XMLHttpRequest() results in window.URL.createObjectUrl is not a function error

I'm trying to download an audio file using XMLHttpRequest from a Spring controller without any success. There are numerous SO posts that describe different ways to do this but obviously I'm missing something. I realize I can just set the audio.src to an url without using AJAX, but for various reasons that won't meet the requirements for the site. I tested that approach and it does work which leads me to believe the controller is returning a valid file.
I've tried lots of different variations on the code below but even though I can see in Firebug that the audio file appears to be returned correctly in the response I always run into the window.URL.createObjectUrl is not a function error. I've gone through all of the javascript libraries and my own javascript and I can't find anywhere that URL was redefined to be something else.
I saw a post somewhere that this error may be returned if the blob is not well-formed - is there any way to check if that's the problem? Otherwise I'm stuck.
Spring controller
#RequestMapping(value = "/getAudio", method = RequestMethod.GET, produces = {MediaType.APPLICATION_OCTET_STREAM_VALUE})
public void getAudio(#RequestParam("audioId") Long audioId, HttpServletResponse response) {
//utility function that copies the audio file to the response output stream
audioUtil.getNoAudioFile(audioId, response);
}
Javascript
function getAudio() {
var url = appContextPath + '/getAudio?audioId=1';
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = 'blob';
xhr.onload = function(e) {
if (this.readyState == 4 && this.status == 200) {
var blob = new Blob([xhr.response], {type: 'audio/ogg'});
var size = blob.size; //this returns the correct # bytes
var type = blob.type; //this returns 'audio/ogg'
var audio = $('.audioCtl').get(0);
//variations I tried - none of them work
//var url = window.URL.createObjectUrl(blob);
//var url = URL.createObjectUrl(blob);
var url = (window.URL || window.webkitURL).createObjectUrl(blob);
audio.src = url;
audio.onload = function(evt) {
(window.URL || window.webkitURL).revokeObjectUrl(audio.src);
}
audio.play();
};
};
};
xhr.send();
}
UPDATE
I decided to remove any possibility that some other parts of the app were somehow causing this error so I took a basic Hello World app and added the controller method to retrieve the audio file and added the appropriate javascript. Same error. So then I found a JSFiddle example for a .png file, added the appropriate code and it worked! Reworking my audio code I ended up with this:
var blob = this.response;
$('.audioCtl').get(0).src=window.URL.createObjectURL(blob);
var audio = $('.audioCtl').get(0);
audio.onload = function(evt) {
window.URL.revokeObjectUrl(audio.src);
};
audio.play();
If I change it back to this, I get the error:
var blob = this.response;
var audio = $('.audioCtl').get(0);
audio.src = window.URL.createObjectUrl(blob);
audio.onload = function(evt) {
window.URL.revokeObjectUrl(audio.src);
};
audio.play();
It didn't matter if I used var blob = new Blob([xhr.response], {type: 'audio/ogg'}); or var blob = this.response;.
I don't have a clue as to why the first one works and the second doesn't. Anyone?
You have asked for a working Ajax example, so here it is:
The controller:
#Controller
public class AjaxController {
#RequestMapping("/register")
#ResponseBody
public String register(#ModelAttribute GameUser user){
System.out.println(user);
return "Ajax call completion...";
}
}
I guess you're asking yourself what is GameUser:
public class GameUser {
private Long userId;
private String firstName;
private String lastName;
private String email;
private String userName;
private String password;
//setter & getters
}
Finally, my jQuery code:
$(loadDomEvents);
function loadDomEvents(){
$("#register").click(doAjaxCall);
}
function doAjaxCall(){
var myData = $("#formAjax").serialize();
var url = "http://localhost:8080";
var dest = $("#urlDest").attr("href");
$.ajax({ url:url + dest, data:myData, method:"POST", success:doSuccess});
return false;
}
function doSuccess(text){
console.log("success");
alert(text);
$("#ajaxAns").text(text);
}
The JSP file:
<a id="urlDest" class="hidden" href="<spring:url value="/register"/>"></a>
<p id="ajaxAns"></p>
<c:url value="/register" var="registerVar"/>
<form method="POST" action="${registerVar}" id="formAjax">
<button type="submit" id="register">Register</button>
</form>
Important issue:
Between the form tag, you need to have fields that correspond to the fields of GameUser. For example:
<input type="text" id="userName" name="userName">
the important part here is the name attribute with a value of userName with match the GameUser field userName, this how #ModelAttribute` does its form binding, by specifying matching name attributes.

Converting a text XHR-Response to arraybuffer

I'm making an XHR request. At the time of making the request, I don't know whether the URL will return an image or not, so I'm setting xhr.responseType="text"
If the response returns with a Content-Type of image/png [or any other image MIME type], I'm making another request with xhr.responseType="arraybuffer". I then use the arraybuffer that's returned to render the image:
var uInt8Array = new Uint8Array(arraybuffer);
var i = uInt8Array.length;
var binaryString = new Array(i);
while (i--) {
binaryString[i] = String.fromCharCode(uInt8Array[i]);
}
var data = binaryString.join('');
var base64 = window.btoa(data);
//use this base64 string to render the image
Is there any way I can avoid making the second request?
I tried doing this -
var buf = new ArrayBuffer(responseText.length);
var bufView = new Uint8Array(buf);
for (var i=0, i<responseText.length; i++) {
bufView[i] = responseText.charCodeAt(i);
}
return buf;
but the responseText isn't the same as the data in the first code sample, and the resultant ArrayBuffer doesn't render the image correctly.
I had the same problem with a PDF being corrupted, but the handler is generic and handles text, JSON and files.
The easiest way to solve this is to do it the other way round: Always make the request with:
xhr.responseType = "blob";
Then when you want the response as text, just convert the binary data to text:
xhr.response.text().then(text => {
// do something
});
Having binary as the default return type I can just convert that to text where needed.

How to upload/POST multiple canvas elements

I have to create an image uploader for a future project (No flash, IE10+, FF7+ etc.) that does image resizing/converting/cropping on the clientside and not on the server.
So I made a javascript interface where the user can 'upload' their files and get resized/cropped in the browser directly, without ever contacting the server. The performance is OK, not that good, but it works.
The endresult is an array of canvas elements. The user can edit/crop the images after they got resized, so I keep them as canvas instead of converting them to jpeg. (Which would worsen the initial performance)
Now this works fine, but I don't know what's the best way to actually upload the finished canvas elements to the server now. (Using a asp.net 4 generic handler on the server)
I have tried creating a json object from all elements containing the dataurl of each canvas.
The problem is, when I got 10-40 pictures, the browser starts freezing when creating the dataurls, especially for images that are larger than 2 megabyte.
//images = array of UploadImage
for (var i = 0; i < images.length; i++) {
var data = document.getElementById('cv_' + i).toDataURL('image/jpg');
images[i].data = data.substr(data.indexOf('base64') + 7);
}
Also converting them to a json object (I am using json2.js) usually crashes my browser. (FF7)
My object
var UploadImage = function (pFileName, pName, pDescription) {
this.FileName = pFileName;
this.Name = pName;
this.Description = pDescription;
this.data = null;
}
The upload routine
//images = array of UploadImage
for (var i = 0; i < images.length; i++) {
var data = document.getElementById('cv_' + i).toDataURL('image/jpg');
images[i].data = data.substr(data.indexOf('base64') + 7);
}
var xhr, provider;
xhr = jQuery.ajaxSettings.xhr();
if (xhr.upload) {
xhr.upload.addEventListener('progress', function (e) {
console.log(Math.round((e.loaded * 100) / e.total) + '% done');
}, false);
}
provider = function () {
return xhr;
};
var ddd = JSON.stringify(images); //usually crash here
$.ajax({
type: 'POST',
url: 'upload.ashx',
xhr: provider,
dataType: 'json',
success: function (data) {
alert('ajax success: data = ' + data);
},
error: function () {
alert('ajax error');
},
data: ddd
});
What would be the best way to send the canvas elements to the server?
Should I send them all at once or one by one?
Uploading files one by one is better. Requires less memory and as soon as one file ready to upload, the upload can be started instead of waiting while all files will be prepared.
Use FormData to send files. Allows to upload files in binary format instead of base64 encoded.
var formData = new FormData;
If Firefox use canvas.mozGetAsFile('image.jpg') instead of canvas.toDataUrl(). Allow to avoid unnecessary conversion from base64 to binary.
var file = canvas.mozGetAsFile('image.jpg');
formData.append(file);
In Chrome use BlobBuilder to convert base64 into blob (see dataURItoBlob function
accepted
After playing around with a few things, I managed to figure this out myself.
First of all, this will convert a dataURI to a Blob:
//added for quick reference
function dataURItoBlob(dataURI) {
// convert base64/URLEncoded data component to raw binary data held in a string
var byteString;
if (dataURI.split(',')[0].indexOf('base64') >= 0)
byteString = atob(dataURI.split(',')[1]);
else
byteString = unescape(dataURI.split(',')[1]);
// separate out the mime component
var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
// write the bytes of the string to a typed array
var ia = new Uint8Array(byteString.length);
for (var i = 0; i < byteString.length; i++) {
ia[i] = byteString.charCodeAt(i);
}
return new Blob([ia], {type:mimeString});
}
From this question):
var blob = dataURItoBlob(canvas.toDataURL('image/jpg'));
formData.append(blob);
And then send the formData object. I'm not sure how to do it in jQuery, but with plain xhr object it like so:
var xhr = new XMLHttpRequest;
xhr.open('POST', 'upload.ashx', false);
xhr.send(formData);
On server you can get files from Files collection:
context.Request.Files[0].SaveAs(...);

Categories