Showing LongBlob photos from MySQL on View by using AngularJS - javascript

Purpose: show different images on my html page by using AngularJS and just one Servlet (images will change depending on request parameters["id" variable])
Code explanation:
I send request to the Servlet by passing id=13 as params (see JS
code)
Servlet will retrieve the photo stored in the DB having id=13
The response value will be passed to the variable "image" on $scope
Problem: The image doesn't appear on my view.
Error Chrome console:
http://localhost:8080/MyProject/%7B%7Bimage%7D%7D 404 (Not Found)
Console.log(response) result:
Object {data: "����..., status:200, config:Object, statusText:"OK"}
Additional info:
If I change "id" assignment from
"Integer.parseInt(request.getParameter("id"))" to "13", my image will be properly shown on the Servlet URL, which means the file is correctly retrieved and encoded.
If I add System.out.println(id) after "int
id=Integer.parseInt(request.getParameter("id"))", it will be shown 13
as value, which means params are correctly passed from JS file to Servlet
The photos I want to show on my view are stored on MySQL as longBlob
-
MY HTML CODE:
...
<img class="images" src="{{image}}"/>
...
-
MY ANGULAR JS CODE:
...
$http.get('./ImagesServlet', {params: {id:13}})
.then(function(response) {
console.log(response)
$scope.image = response.data;
});
...
-
ImagesServlet CODE:
...
int id = Integer.parseInt(request.getParameter("id"));
PoiDao fetcher = new PoiDao();
try {
List<PoiImg> image = fetcher.ImgById(id);
response.setContentType("image/png");
Blob img = image.get(0).getPhoto();
InputStream in = img.getBinaryStream();
int length = (int) img.length();
int bufferSize = 1024;
byte[] buffer = new byte[bufferSize];
while ((length = in.read(buffer)) != -1) {
response.getOutputStream().write(buffer, 0, length);
}
} catch (ClassNotFoundException | SQLException e1) {
e1.printStackTrace();
}
...

JavaScript
If you don't want to change your $http call to expect a Blob directly,
let blob = new Blob([response.data], {type: 'image/jpeg'});
Otherwise, pass responseType: 'blob' into it so response.data will start as a Blob
Then convert this to a URL you can access from the HTML page
let url = URL.createObjectURL(blob); // note this will prevent GC of blob
Finally if you want to be clean, when you're done with this image
URL.revokeObjectURL(url); // frees blob to be GCd

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.

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.

Using data URI instead of blob in TinyMCE 4 image upload

Using TinyMCE 4, I am trying to do a basic local file picker such as the one used in their example.
After running their example, I noticed that the generated image source is a blob opposed to a base64.
So my question: is it possible to use base64 instead of a blob?
I thought the first argument of the file_picker_callback callback would be used as the source of the image, so I tweaked the code using this answer where I pass the data URI as the first argument.
file_picker_types: 'image',
// and here's our custom image picker
file_picker_callback: function (cb, value, meta) {
var input = document.createElement('input');
input.setAttribute('type', 'file');
input.setAttribute('accept', 'image/*');
// Note: In modern browsers input[type="file"] is functional without
// even adding it to the DOM, but that might not be the case in some older
// or quirky browsers like IE, so you might want to add it to the DOM
// just in case, and visually hide it. And do not forget do remove it
// once you do not need it anymore.
input.onchange = function() {
var file = this.files[0];
var reader = new FileReader();
reader.onload = function () {
// Note: Now we need to register the blob in TinyMCEs image blob
// registry. In the next release this part hopefully won't be
// necessary, as we are looking to handle it internally.
//var id = 'blobid' + (new Date()).getTime();
//var blobCache = tinymce.activeEditor.editorUpload.blobCache;
//var base64 = reader.result.split(',')[1];
//var blobInfo = blobCache.create(id, file, base64);
//blobCache.add( blobInfo );
// call the callback and populate the Title field with the file name
cb(reader.result, { title: 'hola' });
};
reader.readAsDataURL( file );
};
input.click();
}
However it did not work and instead converted the source into a blob e.g.
<img src="blob:null/c8e90adb-4074-45b8-89f4-3f28c66591bb" alt="" />
If I pass a normal string e.g. test.jpg, it will generate
<img src="test.jpg" alt="" />
The blob: format you see is actually a Base64 encoded binary image. If you were to post the content of TinyMCE to the server you would indeed get the Base64 data.
You can force TinyMCE to immediately send that image to your server to get converted to a "regular" image by following these steps:
https://www.tinymce.com/docs/advanced/handle-async-image-uploads/
Add the below code inside the tinymce\plugins\quickbars\plugin.js at the position as shown in the image
$.ajax({
url: 'saveupload', // Upload Script
enctype : 'multipart/form-data',
type: 'post',
data: {"imageString":base64,"imageType":blob.type,"imageName": blob.name},
success: function(responseText) {
var myJSON = JSON.parse(responseText);
editor.insertContent(editor.dom.createHTML('img', { src: myJSON }));
},
error : function(xhr, ajaxOptions, thrownError) {
}
});
Note: If you ur using minified version convert the same into minified version using any minified tools (e.g: yuicompressor)
I am upload images to the apache
servlet code is below
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, java.io.IOException {
tbaService = new TBAServiceImpl();
File f = new File("path");
response.setContentType("text/html");
PrintWriter out = response.getWriter();
Map<String, String[]> parameterNames = request.getParameterMap();
Gson gson = new Gson();
HttpSession session = request.getSession(true);
long timeinMill = new Date().getTime();
String uniqueFileName = "local_"+timeinMill+"_"+parameterNames.get("imageName")[0].replace(" ", "_");
String fileType = parameterNames.get("imageType")[0].split("/")[1];
try {
BufferedImage image = null;
byte[] imageByte;
BASE64Decoder decoder = new BASE64Decoder();
imageByte = decoder.decodeBuffer(parameterNames.get("imageString")[0]);
ByteArrayInputStream bis = new ByteArrayInputStream(imageByte);
image = ImageIO.read(bis);
bis.close();
// write the image to a file
File outputfile = new File(filePath+uniqueFileName); //filePath = C:/Apache/htdocs/tba/images/
ImageIO.write(image, fileType, outputfile);
out.print(gson.toJson(uploadUrl+uniqueFileName)); //uploadUrl=http://localhost/test/images/
out.flush();
out.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}

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