how to save a image from js client to blobstore? - javascript

I want to save a image from my browser into google blobstore.
What I already tried:
Transform my img into a base64 string:
reader = new FileReader();
reader.readAsBinaryString(file);
call my Endpoint Function to send it to google app engine.
var request = gapi.client.helloworldendpoints.uploadImage({'imageData': __upload.imageData, 'fileName': __upload.fileName, 'mimeType': __upload.mimeType, 'size': __upload.size});
request.execute(
function (result) {
console.log("Callback:");
console.log(result);
}
);
My EndPoint in Java looks like this:
#ApiMethod(name = "uploadImage", path = "/uploadImage", httpMethod = "POST")
public ImageUploadRequest uploadImage(#Named("imageData") byte[] imageData, #Named("fileName") String fileName, #Named("mimeType") String mimeType, #Named("size") float size) {
return new ImageUploadRequest(imageData, fileName, mimeType, size);
}
The problem is, that my endpoint seems to be unable to handle the transfer of my base64. i always get 503 backend error
What would be the best way to send the data from my js client via app engine to blobstore?

UPDATE
The main Problem is solved. I managed to upload the blob.
Javascript
var request = gapi.client.helloworldendpoints.uploadImage({
'imageData': __upload.imageData,
'fileName': __upload.fileName,
'mimeType': __upload.mimeType,
'size': __upload.size
});
Java Endpoint
public ImageUploadRequest uploadImage(
Request imageData,
#Named("fileName") String fileName,
#Named("mimeType") String mimeType,
#Named("size") float size
) { ... }
Request is just this
public class Request {
public Blob image;
}
My next Problem is the following
How can i send a MultipartRequest from my Java Endpoint at GAE to my UploadServlet to create a blobkey and save the data into blobstorage, since Blobstorage only accepts data send to servlet?

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.

How to convert base64 content to file object in client side and send it to controller

I am using the following code to send a list of files to the backend:
var formdata = new FormData();
if(fileObjectList.length>0){
Object.keys(fileObjectList).forEach(i => {
formdata.append('file' + i, fileObjectList[i]);
});
}
formdata.append('requestModel', JSON.stringify(request));
req.open("POST", 'contorller');
req.send(formdata);
The controller converts the file to base64 data.
To send the data via email, we have to attach the content as base64,
which I again send to the controller as a file object.
You can use jszip to add files in a zip and send whole doc as base64 in single request. check the below link for more information jszip
var jszip = new ZipHandler;
var formdata = new FormData();
if(fileObjectList.length>0){
Object.keys(fileObjectList).forEach(i => {
jszip.addFile(`${fileObjectList[i]}.fileTypeExt`, '(buffer|base64)');
});
};
var zipcomplete = await t.generate({
base64: !0,
compression: "DEFLATE"
});
formdata.append('fileDataZip', zipcomplete);
formdata.append('requestModel', JSON.stringify(request));
req.open("POST", 'contorller');
req.send(formdata)
by using C# use below code to save base64 file
System.IO.File.WriteAllBytes("/fileDataZip.zip", Convert.FromBase64String(fileDataZip));
By using nodejs utilize the below code to save base64 file
require("fs").writeFile("fileDataZip.zip", fileDataZip, 'base64');

Insert bytes[] in database (Hibernate, JPA, Spring) from input type=file

I've been fighting for days with this problem:insert file into database with Spring Boot, JPA Hibernate and Vue.js frontend.
(Yes I know it's better to store the location to retrieve the file and not the file itself, but I have to store the file, so move on.) I tried different solutions but I didn't manage. First I passed the file path from fontend to backend as a normal field of my json data and used:
String path= json.get("file_name").asText();
File file = new File(path);
byte[] fileInBytes = new byte[(int) file.length()];
FileInputStream fileInputStream = new FileInputStream(file);
fileInputStream.read(fileInBytes);
fileInputStream.close();
c.setFile(fileInBytes);
It worked only if I passed the explicit path, because from my HTML input type=file I always got C:/fakepath/filename and the backend didn't find the file obviously. Is there any way to pass the explicit path? I've searched for a while but I couldn't find a solution, so I changed my mind.
Now I'm passing the base64 encode of the file from the frontend in Vue with this code (thanks to How to convert file to base64 in JavaScript?):
getBase64(file, onLoadCallback) {
return new Promise(function(resolve, reject) {
var reader = new FileReader();
reader.onload = function() { resolve(reader.result); };
reader.onerror = function(error) {
console.log('Error when converting file to base64: ', error);
};
reader.readAsDataURL(file);
});
},
uploadFile: function(event) {
this.input_file = document.getElementById("challenge_file").files[0];
this.base64_file = this.getBase64(this.input_file);
this.base64_file.then(function(result) {
console.log(result);
this.File=JSON.stringify({'file': this.base64_file});
axios.post("/uploadfile",
this.File,
{ headers: {
'Content-type': 'application/json',
}
}).then(function(response){
location.reload(true);
}).catch(function (error){
console.log(error);
});
});
}
this code works and I get a base64 string also in the backend, in which I try to convert it in bytes[] because my file is a #Lob private byte[] file;. This is my code in the backend controller:
System.out.println(json.get("file"));
//print "data:text/plain;base64,aVZCT1J..."
int init= json.get("file").asText().lastIndexOf(",");
String base64file=json.get("file").asText().substring(init+1);
//I get only the part after 'base64,' *(see below)
System.out.println(base64file);
byte[] decodedByte = Base64.getDecoder().decode(base64file);
//I decode it into bytes[]
c.setFile(decodedByte);
*I get only the the part after 'base64,' otherwise if I use all the String I get this error: enter java.lang.IllegalArgumentException: Illegal base64 character 3a
This code has no errors, but the Blob in the database is empty, while in the first way I could open the file preview from Hibernate, but only if I wrote the correct real path, not retrieving it from the input.
Any suggestion? What should I do?
SOLVED:
Thanks to an answer to this question I changed my backend into:
System.out.println(json.get("file"));
String data= json.get("file").asText();
String partSeparator = ",";
if (data.contains(partSeparator)) {
String encodedImg = data.split(partSeparator)[1];
byte[] decodedImg = Base64.getDecoder().decode(encodedImg.getBytes(StandardCharsets.UTF_8));
c.setFile(decodedImg);
}
and now I see the correct file in the db.

Java: Image upload with JavaScript - File is damaged, corrupted or too large

I am using Spring Boot as backend server and I have a JavaScript frontend.
For sending data between front- and backend I'm using the Axios library, which usually works pretty fine.
The Problem:
The image looks like this in the (Chrome) browser console:
It's a very very long alphanumeric string and that's what I send to the server with the following code:
static uploadFiles(files) {
const data = new FormData();
Object.keys(files).forEach(key => {
data.append("files", new Blob([files[key]], { type: 'image/jpeg' }));
});
const url = API_URL + "uploadFiles";
return axios.post(url, data, RestServices.getAuth({
"Content-Type": "multipart/form-data;boundary=gc0p4Jq0M2Yt08jU534c0p"
}));
}
I have no idea what the boundary thing does but it worked to receive a file in the backend tho...
On backend (spring) side I successfully receive an array of MultipartFiles:
#RequestMapping(value = "/uploadFiles", method = RequestMethod.POST)
#ResponseBody
public boolean uploadFiles(HttpServletRequest request, #RequestParam("files") MultipartFile[] files) throws IOException {
String filePath = Thread.currentThread().getContextClassLoader().getResource("assets/images/").getFile();
InputStream inputStream;
OutputStream outputStream;
for(MultipartFile file : files) {
File newFile = new File(filePath + file.getOriginalFilename() + ".jpg");
inputStream = file.getInputStream();
if (!newFile.exists() && newFile.createNewFile()) {
outputStream = new FileOutputStream(newFile);
int read;
byte[] bytes = new byte[1024];
while ((read = inputStream.read(bytes)) != -1) {
outputStream.write(bytes, 0, read);
}
}
System.out.println(newFile.getAbsolutePath());
}
return true;
}
I've also tried it file.transferTo(newFile); instead of in- and outputstreams - which didn't work either.
After that I get the following output, which means that the image was saved successfully:
/path/to/blob.jpg
If I check the path where the file was uploaded, there is a file named blob.jpg, but if I open it, the windows photo viewer has the following problem:
I've opened the image before and after upload with notepad++:
Before upload:
I think this is a byte array, but If I open the image after upload I get exactly the output of the browser. This means it didn't get converted to a byte array (correct me if I'm wrong) and I believe that's why it's a corrupt image...
My questions are:
What's the problem?
How can I fix it?
I really tried everything which crossed my mind but I ran out of ideas.
Thanks for your help! :-)
I've read following *related* questions (but they **don't** have an answer):
[Question1][5], [Question2][6], and **many** more...
I've finally found an answer on my own!
I think the problem was that I used the e.target.result (which is used to show the image on the frontend) but insted I had to use the JS File object. The standard HTML 5 file input fields return those File objects (as I've read here).
The only thing I had to do now is to make a FormData object, append the File Object, set the FormData as Body and set the Content-Type header and that's it!
const data = new FormData();
data.append("files", fileObject);
return axios.post(url, data, {
"Content-Type": "multipart/form-data"
});
Those JS File Objects are recognized from Java as Multipart files:
#RequestMapping(value = "/uploadFiles", method = RequestMethod.POST)
#ResponseBody
public boolean uploadFiles(HttpServletRequest request, #RequestParam("files") MultipartFile[] files) {
boolean transferSuccessful = true;
for (MultipartFile file : files) {
String extension = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf('.'));
String newFileName = genRandomName() + extension; //set unique name when saving on server
File newFile;
File imageFolder = new File(imageBasePath);
//check if parent folders exist else create it
if(imageFolder .exists() || imageFolder .mkdirs()) {
while ((newFile = new File(imageFolder .getAbsolutePath() + "\\" + newFileName)).exists()) {
newFileName = genRandomName(); //generate new name if file already exists
}
try {
file.transferTo(newFile);
} catch (IOException e) {
e.printStackTrace();
transferSuccessful = false;
}
} else {
LOG.error("Could not create folder at " + imageFolder.getAbsolutePath());
transferSuccessful = false;
}
}
return transferSuccessful;
}
I hope this is helpful :)

image downloads as JSON array of ints but i want to render this image by turning into a byte[]

I have a webservice which returns a byte[] to the client, to show images.
This image is stored in a json object, see fiddle: http://jsfiddle.net/FuGN8/
the array of numerics is assigned to result after i do a simple line of:
result = result["d"];
This is fetched via a AJAX call, so i want to render an image from this data.
Naturally, doing something like:
$("img#mytag").attr("src", result);
would not do what i want.
Is there a javascript command which would do what i am intending?
my Server side code I changed to do:
WebClient wsb = new WebClient();
string url = "...";
byte[] resp = wsb.DownloadData(url);
UTF8Encoding enc = new UTF8Encoding();
return enc.GetString(resp);
but on the client side, since i do not know what the image type would be, i was attempting:
src="data:image/*;base64,"+RET_VAL
and it wasnt doing anything. On a similar note, i also tried:
src="data:image;base64,"+RET_VAL
since the above was doing UTF8 encoding, i also added in a the following:
src:"data:image;base64,"+window.btoa(unescape(encodeURIComponent( RET_VAL )))
You are not using Base64 encoding in your image. Use the method Convert.ToBase64String instead. You could also send the image type in the JSON response in order to apply it to the src attribute. I could get it to work with this code:
[WebMethod]
[ScriptMethod(UseHttpGet = true, ResponseFormat = ResponseFormat.Json)]
public string SendImage()
{
var path = #"C:\teste.png";
byte[] bytes = File.ReadAllBytes(path);
Image image = null;
using (var stream = new MemoryStream(bytes))
image = Image.FromStream(stream);
var json = new Dictionary<string, object>();
json.Add("type", new ImageFormatConverter().ConvertToString(image.RawFormat).ToLower());
json.Add("contents", Convert.ToBase64String(bytes));
return new JavaScriptSerializer().Serialize(json);
}
And this JavaScript code:
$.ajax({
type: 'GET',
url: 'WebService1.asmx/SendImage',
contentType: 'application/json; charset=utf-8',
success: function (response) {
var data = JSON.parse(response.d);
$('<img />').attr('src', 'data:image/' + data.type + ';base64,' + data.contents).appendTo('body');
}
})
Of course you'll have to adapt it as you are using a WebClient to get your image.
The src attribute of your img element expects the image location (its URL), not the actual image bytes.
To put your data as an URL you may use the data URI Scheme. For example, for a .png image:
data:image/png;base64,<your image bytes encoded in base64>

Categories