spring mvc display base64 as image - java

I am using CKEditor WYSWIG as my text editor on my site.
When User paste an image on the editor it is sent in post as <img src="data:image/png;base64,iVBORw0KGgoAAAANsd..." />
I would like to get this base64 string, save it in database and then create endpoint like /image/{id} which will show this image so in post I would not have to put whole base64 string in image source but just url like shown above.
this is how I save the base64 as byte[]:
#RequestMapping(value = {"/main/createpost"}, method = RequestMethod.POST)
public String postPost(Model model, Principal principal,#RequestParam(name="editor-content") String postPayload) throws IOException {
postPayload = checkAndSavePhotos(postPayload);
model.addAttribute("editor",postPayload);
return "createpost";
}
checkAndSavePhotos is checking if editor contains any images and if so it stores it in database:
private String checkAndSavePhotos(String postPayload) throws IOException {
int i =1;
Pattern pattern = Pattern.compile(".*<img src=\".*;base64,(.*?)\".*/>");
Matcher matcher = pattern.matcher(postPayload);
while (matcher.find()) {
PostPhoto postPhoto = new PostPhoto();
byte[] bytes = Base64.getDecoder().decode(matcher.group(i).getBytes());
MultipartFile mf =null;
try {
BufferedImage originalImage = ImageIO.read(new ByteArrayInputStream(bytes));
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(originalImage, "png", baos);
baos.flush();
mf = new MockMultipartFile("test", baos.toByteArray());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
postPhoto.setContent(mf.getBytes());
postPhoto = postPhotoService.save(postPhoto);
}
return null;
}
I have made it this way because on my other form with <input type='file' /> when I was using FileBucket it was enough for me to show fileBucket.getFile().getBytes(); in order to show image. I was trying to create MultipartFile from byte[] and made it the same way.
My Endpoint to show image:
#RequestMapping(value = "/main/postphoto/{imageId}")
#ResponseBody
public byte[] getImage(#PathVariable Long imageId) throws IOException {
PostPhoto image = postPhotoService.findById(imageId);
return image.getContent();
}
Now when I am looking at database content column looks like:
\x89504e470d0a1a0a0000000d49484452000000280000002808060000008cfeb86d0000033f4944415478daed9(...)
while file from filebucket
\377\330\377\341\000\030Exif\000\000II*\000\010\000\000\000\000\000\000\000\000\000\000\000\377\354\000\021Ducky\000\001\000\004\000\000\000A\000\000\377\341\003ohttp://ns.adobe.com/xap/1.0/\000<?xpacket begin="\357\273\277" id="W5M0MpCehiHzreSzNTczkc9d"?> (...)
Can anyone give me a hint how to made it works?

It looks like it was a stupid mistake.
My database column content was type of text so I was storing byte[] as a text, so it's not wierd that the file was not decoded correctly by browser.
Changing database column type to bytea solved problem.

Related

Return a zip (or any file) from the server on the client browser (REST)

So I am using Java for my Server and Angular for the Client. I am currently working on a feature where you can select multiple files from a table and when you press on download, it generates a zip file and downloads it to your browser. As of right now, the server now creates the zip file and I can access it in the server files. All that is left to do is to make it download on the client's browser. (the zip file is deleted after the client downloads it)
After doing some research, I found out that you can use a fileOutputStream to do this. I also saw some tools like retrofit... I am using REST and this is what my code looks like. How would I achieve my goal as simply as possible?
Angular
httpGetDownloadZip(target: string[]): Observable<ServerAnswer> {
const params = new HttpParams().set('target', String(target)).set('numberOfFiles', String(target.length));
const headers = new HttpHeaders().set('token', this.tokenService.getStorageToken());
const options = {
headers,
params,
};
return this.http
.get<ServerAnswer>(this.BASE_URL + '/files/downloadZip', options)
.pipe(catchError(this.handleError<ServerAnswer>('httpGetZip')));
}
Java zipping method
public void getDownloadZip(String[] files, String folderName) throws IOException {
[...] // The method is huge but basically I generate a folder called "Download/" in the server
// Zipping the "Download/" folder
ZipUtil.pack(new File("Download"), new File("selected-files.zip"));
// what do I return ???
return;
}
Java context
server.createContext("/files/downloadZip", new HttpHandler() {
#Override
public void handle(HttpExchange exchange) throws IOException {
if (!handleTokenPreflight(exchange)) { return; }
System.out.println(exchange.getRequestURI());
Map<String, String> queryParam = parseQueryParam(exchange.getRequestURI().getQuery());
String authToken = exchange.getRequestHeaders().getFirst("token");
String target = queryParam.get("target") + ",";
String[] files = new String[Integer.parseInt(queryParam.get("numberOfFiles"))];
[...] // I process the data in this entire method and send it to the previous method that creates a zip
Controller.getDownloadZip(files, folderName);
// what do I return to download the file on the client's browser ????
return;
}
});
A possible approach to successfully download your zip file can be the described in the following paragraphs.
First, consider returning a reference to the zip file obtained as the compression result in your downloadZip method:
public File getDownloadZip(String[] files, String folderName) throws IOException {
[...] // The method is huge but basically I generate a folder called "Download/" in the server
// Zipping the "Download/" folder
File selectedFilesZipFile = new File("selected-files.zip")
ZipUtil.pack(new File("Download"), selectedFilesZipFile);
// return the zipped file obtained as result of the previous operation
return selectedFilesZipFile;
}
Now, modify your HttpHandler to perform the download:
server.createContext("/files/downloadZip", new HttpHandler() {
#Override
public void handle(HttpExchange exchange) throws IOException {
if (!handleTokenPreflight(exchange)) { return; }
System.out.println(exchange.getRequestURI());
Map<String, String> queryParam = parseQueryParam(exchange.getRequestURI().getQuery());
String authToken = exchange.getRequestHeaders().getFirst("token");
String target = queryParam.get("target") + ",";
String[] files = new String[Integer.parseInt(queryParam.get("numberOfFiles"))];
[...] // I process the data in this entire method and send it to the previous method that creates a zip
// Get a reference to the zipped file
File selectedFilesZipFile = Controller.getDownloadZip(files, folderName);
// Set the appropiate Content-Type
exchange.getResponseHeaders().set("Content-Type", "application/zip");
// Optionally, if the file is downloaded in an anchor, set the appropiate content disposition
// exchange.getResponseHeaders().add("Content-Disposition", "attachment; filename=selected-files.zip");
// Download the file. I used java.nio.Files to copy the file contents, but please, feel free
// to use other option like java.io or the Commons-IO library, for instance
exchange.sendResponseHeaders(200, selectedFilesZipFile.length());
try (OutputStream responseBody = httpExchange.getResponseBody()) {
Files.copy(selectedFilesZipFile.toPath(), responseBody);
responseBody.flush();
}
}
});
Now the problem is how to deal with the download in Angular.
As suggested in the previous code, if the resource is public or you have a way to manage your security token, including it as a parameter in the URL, for instance, one possible solution is to not use Angular HttpClient but an anchor with an href that points to your ever backend handler method directly.
If you need to use Angular HttpClient, perhaps to include your auth tokens, then you can try the approach proposed in this great SO question.
First, in your handler, encode to Base64 the zipped file contents to simplify the task of byte handling (in a general use case, you can typically return from your server a JSON object with the file content and metadata describing that content, like content-type, etcetera):
server.createContext("/files/downloadZip", new HttpHandler() {
#Override
public void handle(HttpExchange exchange) throws IOException {
if (!handleTokenPreflight(exchange)) { return; }
System.out.println(exchange.getRequestURI());
Map<String, String> queryParam = parseQueryParam(exchange.getRequestURI().getQuery());
String authToken = exchange.getRequestHeaders().getFirst("token");
String target = queryParam.get("target") + ",";
String[] files = new String[Integer.parseInt(queryParam.get("numberOfFiles"))];
[...] // I process the data in this entire method and send it to the previous method that creates a zip
// Get a reference to the zipped file
File selectedFilesZipFile = Controller.getDownloadZip(files, folderName);
// Set the appropiate Content-Type
exchange.getResponseHeaders().set("Content-Type", "application/zip");
// Download the file
byte[] fileContent = Files.readAllBytes(selectedFilesZipFile.toPath());
byte[] base64Data = Base64.getEncoder().encode(fileContent);
exchange.sendResponseHeaders(200, base64Data.length);
try (OutputStream responseBody = httpExchange.getResponseBody()) {
// Here I am using Commons-IO IOUtils: again, please, feel free to use other alternatives for writing
// the base64 data to the response outputstream
IOUtils.write(base64Data, responseBody);
responseBody.flush();
}
}
});
After that, use the following code in you client side Angular component to perform the download:
this.downloadService.httpGetDownloadZip(['target1','target2']).pipe(
tap((b64Data) => {
const blob = this.b64toBlob(b64Data, 'application/zip');
const blobUrl = URL.createObjectURL(blob);
window.open(blobUrl);
})
).subscribe()
As indicated in the aforementioned question, b64toBlob will look like this:
private b64toBlob(b64Data: string, contentType = '', sliceSize = 512) {
const byteCharacters = atob(b64Data);
const byteArrays = [];
for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
const slice = byteCharacters.slice(offset, offset + sliceSize);
const byteNumbers = new Array(slice.length);
for (let i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
}
const blob = new Blob(byteArrays, {type: contentType});
return blob;
}
Probably you will need to slightly modify the httpGetDownloadZip method in your service to take into account the returned base 64 data - basically, change ServerAnswer to string as the returned information type:
httpGetDownloadZip(target: string[]): Observable<string> {
const params = new HttpParams().set('target', String(target)).set('numberOfFiles', String(target.length));
const headers = new HttpHeaders().set('token', this.tokenService.getStorageToken());
const options = {
headers,
params,
};
return this.http
.get<string>(this.BASE_URL + '/files/downloadZip', options)
.pipe(catchError(this.handleError<ServerAnswer>('httpGetZip')));
}
You could try using responseType as arraybuffer.
Eg:
return this.http.get(URL_API_REST + 'download?filename=' + filename, {
responseType: 'arraybuffer'
});
In My Project including both front end (angular) and back end (java).
We used the below solution ( hope it work for you ):
Angular:
https://github.com/eligrey/FileSaver.js
let observable = this.downSvc.download(opts);
this.handleData(observable, (data) => {
let content = data;
const blob = new Blob([content], { type: 'application/pdf' });
saveAs(blob, file);
});
Java:
public void download(HttpServletRequest request,HttpServletResponse response){
....
response.setHeader("Content-Disposition",
"attachment;filename=\"" + fileName + "\"");
try (
OutputStream os = response.getOutputStream();
InputStream is = new FileInputStream(file);) {
byte[] buf = new byte[1024];
int len = 0;
while ((len = is.read(buf)) > -1) {
os.write(buf, 0, len);
}
os.flush();
}
You can still use HttpServletRequest on the server...
Then get its OutputStream and write to it.
#RequestMapping(method = RequestMethod.POST , params="action=downloadDocument")
public String downloadDocument(#RequestParam(value="documentId", required=true) String documentId,
HttpServletRequest request,
HttpServletResponse response )
{
try {
String docName = null;
String documentSavePath = getDocumentSavePath();
PDocument doc = mainService.getDocumentById(iDocumentId);
if(doc==null){
throw new RuntimeException("document with id: " + documentId + " not found!");
}
docName = doc.getName();
String path = documentSavePath + ContextUtils.fileSeperator() + docName;
response.setHeader("Content-Disposition", "inline;filename=\"" + docName + "\"");
OutputStream out = response.getOutputStream();
response.setContentType("application/word");
FileInputStream stream = new FileInputStream(path);
IOUtils.copy(stream, out);
out.flush();
out.close();
} catch(FileNotFoundException fnfe){
logger.error("Error downloading document! - document not found!!!! " + fnfe.getMessage() , fnfe);
} catch (IOException e) {
logger.error("Error downloading document!!! " + e.getMessage(),e);
}
return null;
}

Testing a Rest controller post request

So i have a method tha takes and user Id and a multipartFile and uploads it to aws and im trying to test it but i get and error java.lang.IllegalArgumentException: Not enough variable values available to expand
this is the method to upload the file
public void uploadUserProfileImage(#PathVariable("userProfileId")UUID userProfileId ,
#RequestParam("file")MultipartFile file){
userProfileService.uploadUserProfileImage(userProfileId,file);
}
and this is the test i came up with, im trying to learn how to test my methods.
#Test
void uploadUserProfileImage() throws Exception {
BufferedImage img;
img = ImageIO.read(new File("src/main/java/images/bane.png"));
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ImageIO.write(img, "file", byteArrayOutputStream);
byte[] byteArryaImg = byteArrayOutputStream.toByteArray();
//given
MockMultipartFile file = new MockMultipartFile("file",
"file",
MediaType.IMAGE_PNG_VALUE,
byteArryaImg);
UserProfileModel user = new UserProfileModel(UUID.randomUUID(), "luad", "ada");
String userProfileId = user.getUserProfileId().toString();
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
mockMvc.perform(multipart("/{userProfileId}/image/upload")
.file(file)
.accept(MediaType.IMAGE_PNG))
.andExpect(status()
.isOk());
}
I suppose that you may use such as following;
MockMultipartFile mockMultipartFile = new MockMultipartFile("image-name", "mock-file.png", "image/jpeg", "image".getBytes());
MockHttpServletRequestBuilder mockRequest = fileUpload("/{userProfileId}/image/upload").file(mockMultipartFile)
you need to import fileUpluad method from;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.fileUpload;
then, just perform such as;
mockMvc.perform(mockRequest).andExpect(status().isOk());

Upload an image to server in Android without encoding to Base64 using Volley

I have an uploader in android which uploads an image file(which is encrypted by an algorithim to 400bytes) but the file has to be decoded to base64 string to be saved to a mysql db as a string and upload the image to a folder.
The problem is that after decoding the file to base64 string it looses its byte encrypted algorithm format.(The image size reduces from 400 bytes to less)
This is what i have done:
Bitmap b = this.toGrayscale(mRegisterImage); //this image file is 400byte encrypted
uploadImage(b);
This is the function uploadimage
public String getStringImage(Bitmap bmp) { //This is what converts the image to 64encoded format string
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bmp.compress(Bitmap.CompressFormat.JPEG, 100, baos); //Thos is what compresses image hence loosing bytes
byte[] imageBytes = baos.toByteArray();
String encodedImage = Base64.encodeToString(imageBytes, Base64.DEFAULT);
return encodedImage;
}
private void uploadImage(final Bitmap bmp) {
// Showing the progress dialog
try {
final ProgressDialog loading = ProgressDialog.show(this,
"Uploading fingerprint...", "Please wait...", false, false);
StringRequest stringRequest = new StringRequest(
Request.Method.POST, Config.url,
new Response.Listener<String>() {
#Override
public void onResponse(String s) {
// Disimissing the progress dialog
debugMessage("Return Message: " + s);
loading.dismiss();
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError volleyError) {
try { // Dismissing the progress dialog
loading.dismiss();
// Showing toast
debugMessage(volleyError.getMessage());
} catch (Exception r) {
debugMessage("onerrorresponse: "+volleyError.getMessage());
}
}
}) {
#Override
protected Map<String, String> getParams()
throws AuthFailureError {
// Converting Bitmap to String
String image = getStringImage(bmp);
// Creating parameters
Map<String, String> params = new Hashtable<String, String>();
// Adding parameters
params.put("image", image);
return params;
}
};
// Creating a Request Queue
RequestQueue requestQueue = Volley.newRequestQueue(this);
// Adding request to the queue
requestQueue.add(stringRequest);
} catch (Exception r) {
debugMessage("out: "+r.getMessage());
}
}
This is the PHP CODE:
$sql ="SELECT id FROM fingerprint ORDER BY id ASC";
$res = mysqli_query($con,$sql);
$id = 0;
while($row = mysqli_fetch_array($res)){
$id = $row['id'];
}
$path = "$id.png";
$actualpath = "http://simplifiedcoding.16mb.com/PhotoUploadWithText/$path";
$sql = "INSERT INTO fingerprint (value) VALUES ('$actualpath')";
if(mysqli_query($con,$sql)){
file_put_contents($path,base64_decode($image));
echo "Successfully Uploaded";
}
Everything works quite fine but is there a way that i can upload the actual image without converting it to a string in android using volley library. The above code compresses the image to the string, How do i change this compression so that the bytes arent lost
Basically no, because Http message body can carry byte data only so can't send any other form of this large data through http protocol.
Http transfer data by using TCP which use commonly switching techniques in this case. Protocols either format data in bit or byte level where byte level is mostly used so if you looking for another way then you are out of luck today
So even if you are using StringRequest and passing data as string parameter eventually your every parameter is converted to byte array type so you can't escape from converting data to bytes.

How to retrieve images from server to user using Struts 2

I have a Product entity, which has a imageUrl String field.
Products images after obtaining from user will be saved in directory:
System.getProperty("user.home") + "shop/data/product/"
And when user wants to see some Product I need to get this image from "user.home"+... to JSP page.
I've tried to read the image into the byte array, convert it to Base64 encoding, and then refer in JSP like this:
<img alt="image from user home" src="data:image/png, base64;${requestScope.image}">
But this solution is not working, and as far as I understand, it has a limitation on image size.
Could you suggest me a way how to do such thing?
Try this ( i think you have some typo )
<img alt="image from user home" src="data:image/png;base64,${requestScope.image}">
Also use this site: http://www.askapache.com/online-tools/base64-image-converter/ to make sure that your output Base64 code is correct.
There's an example of ImageAction that serves image from the file system. It's called
Struts 2 dynamic image example.
Instead of base64 encoding/decoding which increases the content length two times and slows down page loading you can use the action that returnes image bytes from the file. It could be a database, in this way it should fetch bytes from Blob.
In your <img> tag that is using src attribute can contain the URL to the action that returns response with a header Content-Type: image/jpeg and bytes written to the body.
This is the code of the ImageAction:
#Result(type = "stream", params = {"contentType", "${type}"})
public class ImageAction extends ActionSupport implements ServletRequestAware {
byte[] imageInByte = null;
String imageId;
private HttpServletRequest servletRequest;
private final static String type = "image/jpeg";
public getInputStream() { return new ByteArrayInputStream(getCustomImageInBytes()); }
public String getType() { return type; }
private String getFilename() {
return this.filename;
}
public String getImageId() {
return imageId;
}
public void setImageId(String imageId) {
this.imageId = imageId;
}
public ImageAction() {
System.out.println("ImageAction");
}
public byte[] getCustomImageInBytes() {
System.out.println("imageId" + imageId);
BufferedImage originalImage;
try {
originalImage = ImageIO.read(getImageFile(this.imageId));
// convert BufferedImage to byte array
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(originalImage, "jpeg", baos);
baos.flush();
imageInByte = baos.toByteArray();
baos.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return imageInByte;
}
private File getImageFile(String imageId) {
String filePath = servletRequest.getSession().getServletContext().getRealPath("/");
File file = new File(filePath + "/Image/", imageId);
System.out.println(file.toString());
return file;
}
#Override
public void setServletRequest(HttpServletRequest request) {
this.servletRequest = request;
}
}
This action supposed to have configuration created by convention-plugin. So it could be used in HTML like this
<img src="<s:url action='Image?imageId=darksouls.jpg' />" alt=""/>
So Alireza Fattahi was right that I had mistakes in my code. The first one is typo in img tag (see answer by Alireza Fattahi), the second one is after reading image to bytes array
byte[] image = ...;
I used
Base64.getEncoder().encode(image);
instead of
Base64.getEncoder().encodeToString(image));
So eventually this method with returning Base64 encoded image works. If there is a better choices - please left comments and answers.

Error delivering a csv file through the browser

What my application is doing is creating a large csv file (its a report) and the idea is to deliver the contents of the csv file without actually saving a file for it. Here's my code
String csvData; //this is the string that contains the csv contents
byte[] csvContents = csvData.getBytes();
response.contentType = "text/csv";
response.headers.put("Content-Disposition", new Header(
"Content-Disposition", "attachment;" + "test.csv"));
response.headers.put("Cache-Control", new Header("Cache-Control",
"max-age=0"));
response.out.write(csvContents);
ok();
The csv files that are being generated are rather large and the error i am getting is
org.jboss.netty.handler.codec.frame.TooLongFrameException: An HTTP line is larger than 4096 bytes.
Whats the best way to overcome this issue?
My tech stack is java 6 with play framework 1.2.5.
Note: the origin of the response object is play.mvc.Controller.response
Please use
ServletOutputStream
like
String csvData; //this is the string that contains the csv contents
byte[] csvContents = csvData.getBytes();
ServletOutputStream sos = response.getOutputStream();
response.setContentType("text/csv");
response.setHeader("Content-Disposition", "attachment; filename=test.csv");
sos.write(csvContents);
We use this to show the results of an action directly in the browser,
window.location='data:text/csv;charset=utf8,' + encodeURIComponent(your-csv-data);
I am not sure about the out of memory error but I would at least try this:
request.format = "csv";
renderBinary(new ByteArrayInputStream(csvContents));
Apparently netty complains that the http-header is too long - maybe it somehow thinks that your file is part of the header, see also
http://lists.jboss.org/pipermail/netty-users/2010-November/003596.html
as nylund states, using renderBinary should do the trick.
We use writeChunk oursleves to output large reports on the fly, like:
Controller:
public static void getReport() {
final Report report = new Report(code, from, to );
try {
while (report.hasMoreData()) {
final String data = await(report.getData());
response.writeChunk(data);
}
} catch (final Exception e) {
final Throwable cause = e.getCause();
if (cause != null && cause.getMessage().contains("HTTP output stream closed")) {
logger.warn(e, "user cancelled download");
} else {
logger.error(e, "error retrieving data");
}
}
}
in report code
public class Report {
public Report(final String code, final Date from, final Date to) {
}
public boolean hasMoreData() {
// find out if there is more data
}
public Future<String> getData() {
final Job<String> queryJob = new Job<String>() {
#Override
public String doJobWithResult() throws Exception {
// grab data (e.g read form db) and return it
return data;
}
};
return queryJob.now();
}
}

Categories