Spring response entity image - spring

I wrote a rest controller to return an image associated with a primary key. Now I wanted to load this image in the browser and I am running into issues:
(1) If I type a GET URL to the image the browser (FireFox and Chrome) don't display the image but they are seeing all the headers properly. Additionally firefox says "The image cannot be displayed because it contains errors"
(2) If I used XMLHttpRequest to create get the image using the URL I get the image but it displays only partially (the bottom half is cut off and is set to transparent).
#GetMapping("/{featureId}/loadImage")
public ResponseEntity<byte []> loadImageForId(#PathVariable long featureId, HttpServletResponse response) throws IOException {
log.info("Getting image for feature id " + featureId);
Feature feature = featureService.getFeatureById(featureId);
File file = featureService.loadImageForFeature(feature);
byte [] imageData = new byte[(int) file.length()];
FileInputStream inputStream = new FileInputStream(file);
inputStream.read(imageData);
inputStream.close();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.parseMediaType(...));
headers.setContentLength(file.length());
response.setHeader("Content-Disposition", "inline; filename=" + file.getName());
return new ResponseEntity<byte[]>(imageData, headers, HttpStatus.OK);
}

if it is working on tomcat, you can use this tomcat's utility class :
import org.apache.tomcat.util.http.fileupload.IOUtils
for example:
response.setContentType("image/jpeg");
InputStream is = new ByteArrayInputStream(imageByteArray);
IOUtils.copy(is,response.getOutputStream());

Okay finally after hours of debugging with curl etc, I was able to verify that the response body was not getting properly encoded image (nothing to do with the headers).
This was caused due to the choice of InputStream and OutputStream objects.
Instead of using FileInputStream I switched to using ImageIO and the underlying BufferedImage to write the output to the ServletResponse as follows:
#GetMapping("/{featureId}/loadImage")
public void loadImageForId(#PathVariable long featureId, HttpServletResponse response) throws IOException {
log.info("Getting image for feature id " + featureId);
Feature feature = featureService.getFeatureById(featureId);
File imageFile = featureService.loadImageForFeature(feature);
MediaType mediaType = MediaType.parseMediaType(Files.probeContentType(imageFile.toPath()));
response.setHeader("Content-Disposition", "inline; filename=" + imageFile.getName());
response.setStatus(HttpStatus.OK.value());
response.setContentType(mediaType.toString());
response.setContentLength((int)imageFile.length());
OutputStream os = response.getOutputStream();
ImageIO.write(ImageIO.read(imageFile), mediaType.getSubtype(), os);
os.flush();
os.close();
}

Related

Problem With outputStream after 1 download

After downloading an Excel with HttpServletResponse the second time i tried to download it dosent work.
public void DatosaExcel(HttpServletResponse response) throws IOException {
writeHeaderLine();
writeDataLines();
ServletOutputStream outputStream = response.getOutputStream();
workbook.write(outputStream);
outputStream.close();
}
#GetMapping("/DescargarExcelEmpleados")
public void DescargarEmpleados(HttpServletResponse response) throws IOException {
response.setContentType("application/octet-stream");
String headerKey = "Content-Disposition";
String headerValue = "attachment; filename=empleados.xlsx";
response.setHeader(headerKey, headerValue);
productService.DatosaExcel(response);
}
those are my Ctrl and function that calls the excel generator but i cant find a way of solving this issue
I'm guessing you are using Apache POI for Excel work here and also guess that nothing happens when you try to generate it the second time (if there is an error, please provide the log).
This is probably because you didn't close the workbook after write. Always
workbook.write(outputStream);
workbook.close(); //<-- Important!!!
I found the answer i have the workbook declaration XSSFWorkbook workbook = new XSSFWorkbook();, on the service class, so after 1 download the object was already full. so a work aroundi used was in the controller function call i initialized the object again.
workbook = new XSSFWorkbook();

Upload to google drive using The files get uploaded, but, only in the root and are always named "Untitled"

I am trying to upload files to Google drive using a REST API
Everything is working fine, but files are uploading into Google Drive only in the root, and with "Untitled" as their name.
public class UploadTODrive {
public static DriveFiles UploadFileTODrive(String accessToken, MultipartFile files) throws IOException {
RestTemplate restTemplate = new RestTemplate();
String requestUri = "https://www.googleapis.com/upload/drive/v2/files";
System.out.println("ContentType==============: " + files.getContentType());
byte[] s=files.getBytes();
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Type", files.getContentType());
headers.setContentLength(0);
//headers.setContentType(MediaType.MULTIPART_FORM_DATA);
headers.add("Authorization", "Bearer " + accessToken);
HttpEntity<byte[]> requestEntity = new HttpEntity<>(s, headers);
ResponseEntity<String> response = restTemplate.exchange(requestUri, HttpMethod.POST, requestEntity,
String.class);
System.out.println("=====>Response: " + response);
Gson gson = new Gson();
DriveFiles driveFiles = gson.fromJson(response.getBody(), DriveFiles.class);
return driveFiles;
}
}
Files are uploaded to google drive in two parts.
The first part is the meta data of the file, this being the name and the mimetype most often and occasionally containing a parent directory.
The second part is the upload of the actual file stream.
It seams that you are uploading the file stream but you have forgotten to post the file metadata. This is posted in the body of your request
This is your HTTP Post request. You need to figure out how to add a post body to this. This is the documentation link to the writable fields that you can post insert#request-body
ResponseEntity<String> response = restTemplate.exchange(requestUri, HttpMethod.POST, requestEntity,
String.class);
Update
found this with a bit of googling
RESTRequest1.AddBody('{"title": "Capture.jpg"}', TRESTContentType.ctAPPLICATION_JSON);

Downloading a PDF file with Spring results in corrupt file

I have implemented a controller in Spring that writes the contents to a pdf file (via input stream) to the ServletOutputStream, however when comparing the contents of the existing file and the downloaded file it appears to be corrupt.
public class DownloadFileController {
#GetMapping(value = "v0/file")
#ResponseBody
public void downloadFile(HttpServletResponse response) {
try (ServletOutputStream outputStream = response.getOutputStream();
InputStream inputStream = getFile()) {
response.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_OCTET_STREAM_VALUE);
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"example.pdf\"");
IOUtils.copy(inputStream, outputStream);
} catch (IOException ex) {
throw ...
}
}
private static InputStream getFile() {
return DownloadFileController.class.getResourceAsStream("/example.pdf");
}
}
The HTTP request is successful and responds with the contents of the PDF, however it is litered with: �
Edit:
The file won't always be PDF, it can also be images, word documents etc.
change
response.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_OCTET_STREAM_VALUE);
to
response.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_PDF);
Set content-type to application/pdf for downloading .pdf files.
You can use this setContentType() method to set the content-type and set the Content-Disposition in the response header using addHeader() method from HttpServletResponse class.
For example :
response.setContentType("application/pdf");
response.addHeader("Content-Disposition", "attachment; filename=example.pdf");
Turns out it was me being an idiot and interpreting the binary response as text which resulted in unknown characters.

Spring returning image as ResponseEntity<byte[]> - image corrupt

I am working on a spring 3.2.7 app and it sends signatures stored in the database as base64 string back to the users browser via a spring controller which outputs the byte array ResponseEntity.
The image is always corrupted and I havent worked on this part of the system as I double checked in svn and the controller has not been touched since the branch I am working on was created.
I am able to convert the base64 string to an image on my desktop and I am also able to the convert the byte array returned to browser into an image before spring steps in.
Below is my code, this was apparently working before so perhaps there is some config change that could cause this?
#RequestMapping(value = "/submissions/signature/{type}/{id}", method = RequestMethod.GET)
#ResponseBody
public ResponseEntity<byte[]> getSignature(#PathVariable String type, #PathVariable Integer id) throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
String base64 = ... gets from db
byte[] bytes = Base64.decodeBase64(base64);
BufferedImage bi = ImageIO.read(new ByteArrayInputStream(bytes));
ImageIO.write(bi, "png", baos);
HttpHeaders headers = new HttpHeaders();
headers.setLastModified(Calendar.getInstance().getTime().getTime());
headers.setCacheControl("no-cache");
headers.setContentType(MediaType.IMAGE_PNG);
headers.setContentLength(baos.toByteArray().length);
//Image as base64 string is ok in converter
System.out.println("BASE 64 IMAGE IS: " + base64);
//This image is created ok on desktop
FileOutputStream fos = new FileOutputStream("C:\\Users\\p\\Desktop\\test_signature.png");
fos.write(bytes);
fos.close();
//This image is created ok on desktop
FileOutputStream fos3 = new FileOutputStream("C:\\Users\\p\\Desktop\\test_signature_baos.png");
fos3.write(bytes);
fos3.close();
return new ResponseEntity<byte[]>(baos.toByteArray(), headers, HttpStatus.OK);
}
The image is being rendered in the browser like:
<img id="userSignature" width="296" height="110" style="border:0px" src="/webapp/service/submissions/signature/user/${subid}" alt="User signature" />
I have not changed this class and I am told that it did work, I am able to create images from both byte arrays and they are ok and looks the same and I able to render the signature string ok for testing like:
<IMG SRC="data:image/png;base64, <base_64_string>" ALT="">
has anyone experienced similar issues or know what could be causing this?
I have now tried sending an image from my file system already created as png and that also fails.
I have now noticed that CSV files do not download properly in the app and they stream in the same way:
#RequestMapping(value = "/results/csv", method = RequestMethod.GET)
#ResponseBody
public ResponseEntity<byte[]> getResultsInCsvFormat() throws IOException {
I have successfully returned file contents with the help of an InputStream:
#RequestMapping(value = "/submissions/signature/{type}/{id}",
method = RequestMethod.GET)
public HttpEntity getFile(HttpServletResponse response,
#PathVariable String type,
#PathVariable Integer id) {
String base64 = "foo"; // get base-64 encoded string from db
byte[] bytes = Base64.decodeBase64(base64);
try (InputStream inputStream = new ByteArrayInputStream(bytes)) {
StreamUtils.copy(inputStream, response.getOutputStream());
response.setContentType(MediaType.IMAGE_PNG_VALUE);
} catch (IOException e) {
// handle
}
return new ResponseEntity(HttpStatus.OK);
}
Note that I'm not using ResponseBody, and in my working version I am using MediaType.APPLICATION_OCTET_STREAM_VALUE rather than the actual file content type.
The accepted solution didn't work for me in Spring Boot 2.x for Base64 images. Here is how I returned a Base64 image:
#GetMapping(value = "/pixels/{id}", produces = MediaType.IMAGE_PNG_VALUE)
#ResponseBody
public byte[] pixelTracking(#PathVariable String id) {
// TODO: do whatever you want here
// return png image
String base64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+ip1sAAAAASUVORK5CYII=";
return Base64.getDecoder().decode(base64);
}
Ok so I have fixed this now with some thanks to beerbajay who showed me that downloading by streaming to the response directly was ok and also that I should look at ByteArrayHttpMessageConverter.
It turned out that I had made a mistake in the spring config and I realised this after reading the spring documentation that told me that ByteArrayHttpMessageConverter is registered automatically when using <mvc:annotation-driven />.
The mvc annotation driven tag had been removed from the config as I thought this was doing the same thing (and I thought it only needed to be declared once within a spring context):
<mvc:annotation-driven content-negotiation-manager="contentNegotiationManager">
<mvc:message-converters>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter" />
</mvc:message-converters>
</mvc:annotation-driven>
It looks like this alone isnt sufficient to set up annotation-driven in the app and I have added back in the tag to the context.xml file.
I dont understand why this needs to be in two places though as all the xml config is loaded through same xml file in what I assumed was the same spring context:
<import resource="config/properties.xml" />
<import resource="config/security.xml" />
<import resource="config/context.xml" />
<import resource="config/content-negotiation.xml" />
<import resource="config/rest-client.xml" />
my solution is :
backend is spring boot and frontend is reactjs
bug is different syntax is backend and frontend : java use ("_","-") and web(reactjs,...) use ("/","+") |
for example : "PGjQOA66-_ne-" convert to "PGjQOA66+//ne/+"
you can testing base64 in this link :https://onlinepngtools.com/convert-base64-to-png
back end code :
ByteArrayOutputStream baos = new ByteArrayOutputStream();
File file = new File(path);/// path : is external directory and local directory in server
FileInputStream imageInFile = new FileInputStream(path);
byte imageData[] = new byte[(int) file.length()];
imageInFile.read(imageData);
String base64 = encodeImage(imageData);
/// 1. Not show if size up 300KB !!! :|
/// 2. base64.replaceAll("_","/").replaceAll("-","+")
byte[] bytes = Base64.decodeBase64(base64.replaceAll("_","/").replaceAll("-","+"));
BufferedImage bi = ImageIO.read(new ByteArrayInputStream(bytes));
ImageIO.write(bi, "png", baos);
HttpHeaders headers = new HttpHeaders();
headers.setLastModified(Calendar.getInstance().getTime().getTime());
headers.setCacheControl("no-cache");
headers.setContentType(MediaType.IMAGE_PNG);
headers.setContentLength(baos.toByteArray().length);
return new ResponseEntity<byte[]>(baos.toByteArray(), headers, HttpStatus.OK);
and other solution for back end:
ByteArrayOutputStream baos = new ByteArrayOutputStream();
File file = new File(path);/// path : is external directory file and local directory file in server, or path get of database.
FileInputStream imageInFile = new FileInputStream(path);
byte imageData[] = new byte[(int) file.length()];
imageInFile.read(imageData);
/// 1. Not show if size up 300KB !!! :| i dont now!
/// 2. base64.replaceAll("_","/").replaceAll("-","+")
String base64 = encodeImage(imageData).replaceAll("_","/").replaceAll("-","+");
return base64;
and reactjs code is :
const [imageData, setImageData] = React.useState({});
setImageData(...request to backend);
<img src={`data:image/png;base64,${imageData}`} alt="This Is alert" />

Httpcache on springboot

I have springboot application that requests an image from url then display it on browser. I want to cache my response using cache-control header.
I use ResponseEntity and already set my header with eTag. I already checked response header in my browser and it shows :
Cache-Control:"max-age=31536000, public"
Content-Type:"image/jpeg;charset=UTF-8"
Etag:"db577053a18fa88f62293fbf1bd4b1ee"
my request also have If-None-Match header. But, I always get 200 status instead of 304.
Here's my code
#RequestMapping(value = "/getimage", method=RequestMethod.GET)
public ResponseEntity<byte[]> getImage() throws Exception {
String url = "www.example.com/image.jpeg";
String eTag = getEtag(url);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(new MediaType("image", "jpeg"));
headers.add("Cache-Control", "max-age=31536000, public");
headers.add("ETag", eTag);
URL imageUrl = new URL(url);
InputStream is = imageUrl.openStream();
BufferedImage imBuff = ImageIO.read(is);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(imBuff, "jpeg", baos);
byte[] image = baos.toByteArray();
return new ResponseEntity<byte[]>(image, headers, HttpStatus.OK);
}
Can anyone help me?
UPDATE
I tried using method described in Unable to cache images served by Spring MVC so my code become :
#RequestMapping(value = "/getimage", method=RequestMethod.GET)
public ResponseEntity<byte[]> getImage() throws Exception {
String url = "www.example.com/image.jpeg";
String eTag = getEtag(url);
URL imageUrl = new URL(url);
HttpURLConnection httpCon = (HttpURLConnection)imageUrl.openConnection();
long lastModified = httpCon.getLastModified();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(new MediaType("image", "jpeg"));
headers.add("Cache-Control", "max-age=31536000, public");
headers.add("ETag", eTag);
headers.add("Last-Modified", new Date(lastModified).toString());
if (webRequest.checkNotModified(eTag)) {
return null;
}
InputStream is = imageUrl.openStream();
BufferedImage imBuff = ImageIO.read(is);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(imBuff, "jpeg", baos);
byte[] image = baos.toByteArray();
return new ResponseEntity<byte[]>(image, headers, HttpStatus.OK);
}
But now i always get 304 status code even if i change the url. I checked webRequest.checkIsNotModified(...) either by eTag and last-modified and it always return true. Did I do something wrong here?
I ended up changing my code. Instead of using webRequest.checkNotModified(eTag) i manually check last-modified and eTag from my resource (which is from s3) and compare it to if-modified-since and if-none-match in request header.
Besides, i also move everything related to http caching in a filter.

Resources