Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Browser displays raw image data of jpeg. What headers should I make sure to have in the response?

I seem to have run into an interesting problem where the browser happily displays an image generated by my Spring MVC web application as long as the URL to my controller is set as SRC of an IMG tag, but displays binary data when navigated directly to the URL.

My Spring MVC Controller generates some BufferedImage (thumbnail), converts it to byte[] and returns it directly into the response body using the @ResponseBody annotation on the controller method. I have registered a org.springframework.http.converter.ByteArrayHttpMessageConverter message converter with the AnnotationMethodHandlerAdapter and even set its supportedMediaTypes property to image/jpeg, which wasn't really helping, so I am setting the Content-Type header on the response manually in the controller method.

<img src="/images/thumbnail?id=1234" />

works fine and displays the image, however navigating directly to the SRC of the image (or right-clicking the image and choosing View Image) ends up displaying the raw data of the image.

Response headers, according to Firebug, received from a request to such URL (http://localhost:8888/images/thumbnail?id=F0snPkvwhtDbl8eutbuq) are:

HTTP/1.1 200 OK
Expires: Wed, 21 Dec 2011 12:39:07 GMT
Cache-Control: max-age=2592000
Content-Type: image/jpeg
Content-Length: 6998
Server: Jetty(6.1.10)

One last word: in Firebug, clicking on the Response tab displays the image :-) What am I missing out? I thought the browser receives the content-type and content-length headers, knows to expect jpeg image, receives raw data for the jpeg, then displays jpeg in the empty browser tab. Somehow FF, and Chrome are displaying raw image data received.

Code I am using:

@RequestMapping(value = "thumbnail", method = { RequestMethod.GET })
@ResponseBody
public byte[] getImageThumbnail(@RequestParam("id") String documentId, HttpServletResponse response) {
    try {
        Document document = documentService.getDocumentById(documentId);
        InputStream imageInputStream = new FileInputStream(document.getUri());
        response.setContentType("image/jpeg");

        BufferedImage img = ImageIO.read(imageInputStream);
        ResampleOp resampleOp = new ResampleOp(THUMBNAIL_DIMENSION);
        BufferedImage thumbnail = resampleOp.filter(img, null);
        return getDataFromBufferedImage(thumbnail);
    } catch (Throwable t) {
        return null; //return no data if document not found or whatever other issues are encountered
    }
}

private byte[] getDataFromBufferedImage(BufferedImage thumbnail) throws IOException {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    try {
        ImageIO.write(thumbnail, "jpg", baos);
        baos.flush();
        return baos.toByteArray();
    } finally {
        baos.close();
    }
}

=== UPDATE === I have followed @BalusC's advice and changed the url that produces the thumbnail to look like an actual .jpg file. This has made some difference in that now I can "Save image as" and the file name is no longer just "thumbnail" but ".jpg" which is good. However, both Chrome and FF (I have not even begun testing on IE) display raw JFIF data when loading the URL into a fresh tab/window. Still, image only displays if the URL is in the SRC attribute of an IMG tag and (thanks to browser caching) when user selects View image in new tab (but only if user does not refresh the tab, refreshing the tab will re-fetch the JPEG and display raw data in the window).

EDIT I have just tested this in IE9 and this is the only browser where it works as expected. I can navigate to the URL directly and keep refreshing the page and I can see my controller being hit and the JPEG is loaded in the browser window. Excellent. Now to figure out what is wrong with the way FF/CR handle the JPEG I am sending.

Additional info I am using Spring version 3.0.6.RELEASE Running web app from Jetty

EDIT I have solved my problem by not using @ResponseBody and BytArrayHttpMessageConverter - I tried the workaround proposed in another thread here on SO - that was to just write the bytes directly into the response output stream: IOUtils.copy(imageInputStream, response.getOutputStream()); This is straightforward and works, I am still curious what was the weird problem with how the browsers would load the reponse in an <img> tag but not directly in a browser window. Anyone can shed more light on this, I would be really curious to find out more. I am leaving this question unanswered for now.

like image 536
Peter Perháč Avatar asked Nov 21 '11 12:11

Peter Perháč


2 Answers

Try to update your annotation to:

@RequestMapping(value = "thumbnail", method = { RequestMethod.GET }, produces = {"image/jpeg"})    
@ResponseBody

Note produces attribute.

Hope that helps.

like image 130
Ivan.Latysh Avatar answered Nov 09 '22 23:11

Ivan.Latysh


I just resolved my problem which is similar so maybe you (or someone else) can make use of it. I have icons stored in psql database:

create table images (image_id int not null primary key, data bytea)

My method in controller is:

    @RequestMapping(value = IMAGE + "/{imageId}", method = GET)
    @ResponseBody
    public ResponseEntity<byte[]> getImage(@PathVariable final Integer imageId) throws IOException {
    Image img = imageService.getImage(imageId);
    HttpHeaders responseHeaders = new HttpHeaders();
    responseHeaders.setContentType(MediaType.IMAGE_PNG);
    responseHeaders.set("Content-Disposition", "attachment");
    return new ResponseEntity<byte[]>(img.getData(), responseHeaders, HttpStatus.OK);
}

And since I'm using thymeleaf my html looks like this:

<img src="#" th:src="@{${navigator.IMAGE} + '/-1'}" />

I put imageId = -1 in link just for testing. It works on FF.

Hope it helps someone :)

like image 32
Iwo Kucharski Avatar answered Nov 09 '22 23:11

Iwo Kucharski